diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c578111..5c02044 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -59,7 +59,7 @@ jobs:
- name: Install linting tools
run: |
python -m pip install --upgrade pip
- pip install ruff black isort
+ pip install ruff black isort mypy
- name: Check code formatting with Black
run: black --check src/aylm tests
@@ -69,3 +69,6 @@ jobs:
- name: Lint with Ruff
run: ruff check src/aylm tests
+
+ - name: Type check with mypy (informational)
+ run: mypy src/aylm --ignore-missing-imports || echo "mypy found issues (non-blocking)"
diff --git a/.output.txt b/.output.txt
new file mode 100644
index 0000000..903b649
--- /dev/null
+++ b/.output.txt
@@ -0,0 +1,4488 @@
+async-dropdown.js.map
+async-graph.js
+async-graph.js.map
+async-highlight.js
+async-highlight.js.map
+async-markdown.js
+async-markdown.js.LICENSE.txt
+async-markdown.js.map
+async-mathjax.js
+async-slider.js
+async-slider.js.map
+async-upload.js
+async-upload.js.LICENSE.txt
+async-upload.js.map
+dash_core_components-shared.js
+dash_core_components-shared.js.LICENSE.txt
+dash_core_components-shared.js.map
+dash_core_components.js
+dash_core_components.js.LICENSE.txt
+dash_core_components.js.map
+express.py
+metadata.json
+package-info.json
+proptypes.js
+./aylm_env/lib/python3.11/site-packages/dash/dcc/__pycache__:
+Button.cpython-311-pytest-9.0.2.pyc
+Button.cpython-311.pyc
+Checklist.cpython-311-pytest-9.0.2.pyc
+Checklist.cpython-311.pyc
+Clipboard.cpython-311-pytest-9.0.2.pyc
+Clipboard.cpython-311.pyc
+ConfirmDialog.cpython-311-pytest-9.0.2.pyc
+ConfirmDialog.cpython-311.pyc
+ConfirmDialogProvider.cpython-311-pytest-9.0.2.pyc
+ConfirmDialogProvider.cpython-311.pyc
+DatePickerRange.cpython-311-pytest-9.0.2.pyc
+DatePickerRange.cpython-311.pyc
+DatePickerSingle.cpython-311-pytest-9.0.2.pyc
+DatePickerSingle.cpython-311.pyc
+Download.cpython-311-pytest-9.0.2.pyc
+Download.cpython-311.pyc
+Dropdown.cpython-311-pytest-9.0.2.pyc
+Dropdown.cpython-311.pyc
+Geolocation.cpython-311-pytest-9.0.2.pyc
+Geolocation.cpython-311.pyc
+Graph.cpython-311-pytest-9.0.2.pyc
+Graph.cpython-311.pyc
+Input.cpython-311-pytest-9.0.2.pyc
+Input.cpython-311.pyc
+Interval.cpython-311-pytest-9.0.2.pyc
+Interval.cpython-311.pyc
+Link.cpython-311-pytest-9.0.2.pyc
+Link.cpython-311.pyc
+Loading.cpython-311-pytest-9.0.2.pyc
+Loading.cpython-311.pyc
+Location.cpython-311-pytest-9.0.2.pyc
+Location.cpython-311.pyc
+Markdown.cpython-311-pytest-9.0.2.pyc
+Markdown.cpython-311.pyc
+RadioItems.cpython-311-pytest-9.0.2.pyc
+RadioItems.cpython-311.pyc
+RangeSlider.cpython-311-pytest-9.0.2.pyc
+RangeSlider.cpython-311.pyc
+Slider.cpython-311-pytest-9.0.2.pyc
+Slider.cpython-311.pyc
+Store.cpython-311-pytest-9.0.2.pyc
+Store.cpython-311.pyc
+Tab.cpython-311-pytest-9.0.2.pyc
+Tab.cpython-311.pyc
+Tabs.cpython-311-pytest-9.0.2.pyc
+Tabs.cpython-311.pyc
+Textarea.cpython-311-pytest-9.0.2.pyc
+Textarea.cpython-311.pyc
+Tooltip.cpython-311-pytest-9.0.2.pyc
+Tooltip.cpython-311.pyc
+Upload.cpython-311-pytest-9.0.2.pyc
+Upload.cpython-311.pyc
+__init__.cpython-311-pytest-9.0.2.pyc
+__init__.cpython-311.pyc
+_imports_.cpython-311-pytest-9.0.2.pyc
+_imports_.cpython-311.pyc
+express.cpython-311-pytest-9.0.2.pyc
+express.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/dash/deps:
+polyfill@7.12.1.min.js react-dom@18.3.1.min.js
+prop-types@15.8.1.js react@16.14.0.js
+prop-types@15.8.1.min.js react@16.14.0.min.js
+react-dom@16.14.0.js react@18.2.0.js
+react-dom@16.14.0.min.js react@18.2.0.min.js
+react-dom@18.2.0.js react@18.3.1.js
+react-dom@18.2.0.min.js react@18.3.1.min.js
+react-dom@18.3.1.js
+./aylm_env/lib/python3.11/site-packages/dash/development:
+__init__.py _py_prop_typing.py
+__pycache__ _r_components_generation.py
+_all_keywords.py base_component.py
+_collect_nodes.py build_process.py
+_generate_prop_types.py component_generator.py
+_jl_components_generation.py update_components.py
+_py_components_generation.py
+./aylm_env/lib/python3.11/site-packages/dash/development/__pycache__:
+__init__.cpython-311-pytest-9.0.2.pyc
+__init__.cpython-311.pyc
+_all_keywords.cpython-311.pyc
+_collect_nodes.cpython-311.pyc
+_generate_prop_types.cpython-311.pyc
+_jl_components_generation.cpython-311.pyc
+_py_components_generation.cpython-311.pyc
+_py_prop_typing.cpython-311.pyc
+_r_components_generation.cpython-311.pyc
+base_component.cpython-311-pytest-9.0.2.pyc
+base_component.cpython-311.pyc
+build_process.cpython-311.pyc
+component_generator.cpython-311.pyc
+update_components.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/dash/html:
+A.py MapEl.py
+Abbr.py Mark.py
+Acronym.py Marquee.py
+Address.py Meta.py
+Area.py Meter.py
+Article.py Nav.py
+Aside.py Nobr.py
+Audio.py Noscript.py
+B.py ObjectEl.py
+Base.py Ol.py
+Basefont.py Optgroup.py
+Bdi.py Option.py
+Bdo.py Output.py
+Big.py P.py
+Blink.py Param.py
+Blockquote.py Picture.py
+Br.py Plaintext.py
+Button.py Pre.py
+Canvas.py Progress.py
+Caption.py Q.py
+Center.py Rb.py
+Cite.py Rp.py
+Code.py Rt.py
+Col.py Rtc.py
+Colgroup.py Ruby.py
+Content.py S.py
+Data.py Samp.py
+Datalist.py Script.py
+Dd.py Section.py
+Del.py Select.py
+Details.py Shadow.py
+Dfn.py Slot.py
+Dialog.py Small.py
+Div.py Source.py
+Dl.py Spacer.py
+Dt.py Span.py
+Em.py Strike.py
+Embed.py Strong.py
+Fieldset.py Sub.py
+Figcaption.py Summary.py
+Figure.py Sup.py
+Font.py Table.py
+Footer.py Tbody.py
+Form.py Td.py
+Frame.py Template.py
+Frameset.py Textarea.py
+H1.py Tfoot.py
+H2.py Th.py
+H3.py Thead.py
+H4.py Time.py
+H5.py Title.py
+H6.py Tr.py
+Header.py Track.py
+Hgroup.py U.py
+Hr.py Ul.py
+I.py Var.py
+Iframe.py Video.py
+Img.py Wbr.py
+Ins.py Xmp.py
+Kbd.py __init__.py
+Keygen.py __pycache__
+Label.py _imports_.py
+Legend.py dash_html_components.min.js
+Li.py dash_html_components.min.js.map
+Link.py metadata.json
+Main.py package-info.json
+./aylm_env/lib/python3.11/site-packages/dash/html/__pycache__:
+A.cpython-311-pytest-9.0.2.pyc Li.cpython-311.pyc
+A.cpython-311.pyc Link.cpython-311-pytest-9.0.2.pyc
+Abbr.cpython-311-pytest-9.0.2.pyc Link.cpython-311.pyc
+Abbr.cpython-311.pyc Main.cpython-311-pytest-9.0.2.pyc
+Acronym.cpython-311-pytest-9.0.2.pyc Main.cpython-311.pyc
+Acronym.cpython-311.pyc MapEl.cpython-311-pytest-9.0.2.pyc
+Address.cpython-311-pytest-9.0.2.pyc MapEl.cpython-311.pyc
+Address.cpython-311.pyc Mark.cpython-311-pytest-9.0.2.pyc
+Area.cpython-311-pytest-9.0.2.pyc Mark.cpython-311.pyc
+Area.cpython-311.pyc Marquee.cpython-311-pytest-9.0.2.pyc
+Article.cpython-311-pytest-9.0.2.pyc Marquee.cpython-311.pyc
+Article.cpython-311.pyc Meta.cpython-311-pytest-9.0.2.pyc
+Aside.cpython-311-pytest-9.0.2.pyc Meta.cpython-311.pyc
+Aside.cpython-311.pyc Meter.cpython-311-pytest-9.0.2.pyc
+Audio.cpython-311-pytest-9.0.2.pyc Meter.cpython-311.pyc
+Audio.cpython-311.pyc Nav.cpython-311-pytest-9.0.2.pyc
+B.cpython-311-pytest-9.0.2.pyc Nav.cpython-311.pyc
+B.cpython-311.pyc Nobr.cpython-311-pytest-9.0.2.pyc
+Base.cpython-311-pytest-9.0.2.pyc Nobr.cpython-311.pyc
+Base.cpython-311.pyc Noscript.cpython-311-pytest-9.0.2.pyc
+Basefont.cpython-311-pytest-9.0.2.pyc Noscript.cpython-311.pyc
+Basefont.cpython-311.pyc ObjectEl.cpython-311-pytest-9.0.2.pyc
+Bdi.cpython-311-pytest-9.0.2.pyc ObjectEl.cpython-311.pyc
+Bdi.cpython-311.pyc Ol.cpython-311-pytest-9.0.2.pyc
+Bdo.cpython-311-pytest-9.0.2.pyc Ol.cpython-311.pyc
+Bdo.cpython-311.pyc Optgroup.cpython-311-pytest-9.0.2.pyc
+Big.cpython-311-pytest-9.0.2.pyc Optgroup.cpython-311.pyc
+Big.cpython-311.pyc Option.cpython-311-pytest-9.0.2.pyc
+Blink.cpython-311-pytest-9.0.2.pyc Option.cpython-311.pyc
+Blink.cpython-311.pyc Output.cpython-311-pytest-9.0.2.pyc
+Blockquote.cpython-311-pytest-9.0.2.pyc Output.cpython-311.pyc
+Blockquote.cpython-311.pyc P.cpython-311-pytest-9.0.2.pyc
+Br.cpython-311-pytest-9.0.2.pyc P.cpython-311.pyc
+Br.cpython-311.pyc Param.cpython-311-pytest-9.0.2.pyc
+Button.cpython-311-pytest-9.0.2.pyc Param.cpython-311.pyc
+Button.cpython-311.pyc Picture.cpython-311-pytest-9.0.2.pyc
+Canvas.cpython-311-pytest-9.0.2.pyc Picture.cpython-311.pyc
+Canvas.cpython-311.pyc Plaintext.cpython-311-pytest-9.0.2.pyc
+Caption.cpython-311-pytest-9.0.2.pyc Plaintext.cpython-311.pyc
+Caption.cpython-311.pyc Pre.cpython-311-pytest-9.0.2.pyc
+Center.cpython-311-pytest-9.0.2.pyc Pre.cpython-311.pyc
+Center.cpython-311.pyc Progress.cpython-311-pytest-9.0.2.pyc
+Cite.cpython-311-pytest-9.0.2.pyc Progress.cpython-311.pyc
+Cite.cpython-311.pyc Q.cpython-311-pytest-9.0.2.pyc
+Code.cpython-311-pytest-9.0.2.pyc Q.cpython-311.pyc
+Code.cpython-311.pyc Rb.cpython-311-pytest-9.0.2.pyc
+Col.cpython-311-pytest-9.0.2.pyc Rb.cpython-311.pyc
+Col.cpython-311.pyc Rp.cpython-311-pytest-9.0.2.pyc
+Colgroup.cpython-311-pytest-9.0.2.pyc Rp.cpython-311.pyc
+Colgroup.cpython-311.pyc Rt.cpython-311-pytest-9.0.2.pyc
+Content.cpython-311-pytest-9.0.2.pyc Rt.cpython-311.pyc
+Content.cpython-311.pyc Rtc.cpython-311-pytest-9.0.2.pyc
+Data.cpython-311-pytest-9.0.2.pyc Rtc.cpython-311.pyc
+Data.cpython-311.pyc Ruby.cpython-311-pytest-9.0.2.pyc
+Datalist.cpython-311-pytest-9.0.2.pyc Ruby.cpython-311.pyc
+Datalist.cpython-311.pyc S.cpython-311-pytest-9.0.2.pyc
+Dd.cpython-311-pytest-9.0.2.pyc S.cpython-311.pyc
+Dd.cpython-311.pyc Samp.cpython-311-pytest-9.0.2.pyc
+Del.cpython-311-pytest-9.0.2.pyc Samp.cpython-311.pyc
+Del.cpython-311.pyc Script.cpython-311-pytest-9.0.2.pyc
+Details.cpython-311-pytest-9.0.2.pyc Script.cpython-311.pyc
+Details.cpython-311.pyc Section.cpython-311-pytest-9.0.2.pyc
+Dfn.cpython-311-pytest-9.0.2.pyc Section.cpython-311.pyc
+Dfn.cpython-311.pyc Select.cpython-311-pytest-9.0.2.pyc
+Dialog.cpython-311-pytest-9.0.2.pyc Select.cpython-311.pyc
+Dialog.cpython-311.pyc Shadow.cpython-311-pytest-9.0.2.pyc
+Div.cpython-311-pytest-9.0.2.pyc Shadow.cpython-311.pyc
+Div.cpython-311.pyc Slot.cpython-311-pytest-9.0.2.pyc
+Dl.cpython-311-pytest-9.0.2.pyc Slot.cpython-311.pyc
+Dl.cpython-311.pyc Small.cpython-311-pytest-9.0.2.pyc
+Dt.cpython-311-pytest-9.0.2.pyc Small.cpython-311.pyc
+Dt.cpython-311.pyc Source.cpython-311-pytest-9.0.2.pyc
+Em.cpython-311-pytest-9.0.2.pyc Source.cpython-311.pyc
+Em.cpython-311.pyc Spacer.cpython-311-pytest-9.0.2.pyc
+Embed.cpython-311-pytest-9.0.2.pyc Spacer.cpython-311.pyc
+Embed.cpython-311.pyc Span.cpython-311-pytest-9.0.2.pyc
+Fieldset.cpython-311-pytest-9.0.2.pyc Span.cpython-311.pyc
+Fieldset.cpython-311.pyc Strike.cpython-311-pytest-9.0.2.pyc
+Figcaption.cpython-311-pytest-9.0.2.pyc Strike.cpython-311.pyc
+Figcaption.cpython-311.pyc Strong.cpython-311-pytest-9.0.2.pyc
+Figure.cpython-311-pytest-9.0.2.pyc Strong.cpython-311.pyc
+Figure.cpython-311.pyc Sub.cpython-311-pytest-9.0.2.pyc
+Font.cpython-311-pytest-9.0.2.pyc Sub.cpython-311.pyc
+Font.cpython-311.pyc Summary.cpython-311-pytest-9.0.2.pyc
+Footer.cpython-311-pytest-9.0.2.pyc Summary.cpython-311.pyc
+Footer.cpython-311.pyc Sup.cpython-311-pytest-9.0.2.pyc
+Form.cpython-311-pytest-9.0.2.pyc Sup.cpython-311.pyc
+Form.cpython-311.pyc Table.cpython-311-pytest-9.0.2.pyc
+Frame.cpython-311-pytest-9.0.2.pyc Table.cpython-311.pyc
+Frame.cpython-311.pyc Tbody.cpython-311-pytest-9.0.2.pyc
+Frameset.cpython-311-pytest-9.0.2.pyc Tbody.cpython-311.pyc
+Frameset.cpython-311.pyc Td.cpython-311-pytest-9.0.2.pyc
+H1.cpython-311-pytest-9.0.2.pyc Td.cpython-311.pyc
+H1.cpython-311.pyc Template.cpython-311-pytest-9.0.2.pyc
+H2.cpython-311-pytest-9.0.2.pyc Template.cpython-311.pyc
+H2.cpython-311.pyc Textarea.cpython-311-pytest-9.0.2.pyc
+H3.cpython-311-pytest-9.0.2.pyc Textarea.cpython-311.pyc
+H3.cpython-311.pyc Tfoot.cpython-311-pytest-9.0.2.pyc
+H4.cpython-311-pytest-9.0.2.pyc Tfoot.cpython-311.pyc
+H4.cpython-311.pyc Th.cpython-311-pytest-9.0.2.pyc
+H5.cpython-311-pytest-9.0.2.pyc Th.cpython-311.pyc
+H5.cpython-311.pyc Thead.cpython-311-pytest-9.0.2.pyc
+H6.cpython-311-pytest-9.0.2.pyc Thead.cpython-311.pyc
+H6.cpython-311.pyc Time.cpython-311-pytest-9.0.2.pyc
+Header.cpython-311-pytest-9.0.2.pyc Time.cpython-311.pyc
+Header.cpython-311.pyc Title.cpython-311-pytest-9.0.2.pyc
+Hgroup.cpython-311-pytest-9.0.2.pyc Title.cpython-311.pyc
+Hgroup.cpython-311.pyc Tr.cpython-311-pytest-9.0.2.pyc
+Hr.cpython-311-pytest-9.0.2.pyc Tr.cpython-311.pyc
+Hr.cpython-311.pyc Track.cpython-311-pytest-9.0.2.pyc
+I.cpython-311-pytest-9.0.2.pyc Track.cpython-311.pyc
+I.cpython-311.pyc U.cpython-311-pytest-9.0.2.pyc
+Iframe.cpython-311-pytest-9.0.2.pyc U.cpython-311.pyc
+Iframe.cpython-311.pyc Ul.cpython-311-pytest-9.0.2.pyc
+Img.cpython-311-pytest-9.0.2.pyc Ul.cpython-311.pyc
+Img.cpython-311.pyc Var.cpython-311-pytest-9.0.2.pyc
+Ins.cpython-311-pytest-9.0.2.pyc Var.cpython-311.pyc
+Ins.cpython-311.pyc Video.cpython-311-pytest-9.0.2.pyc
+Kbd.cpython-311-pytest-9.0.2.pyc Video.cpython-311.pyc
+Kbd.cpython-311.pyc Wbr.cpython-311-pytest-9.0.2.pyc
+Keygen.cpython-311-pytest-9.0.2.pyc Wbr.cpython-311.pyc
+Keygen.cpython-311.pyc Xmp.cpython-311-pytest-9.0.2.pyc
+Label.cpython-311-pytest-9.0.2.pyc Xmp.cpython-311.pyc
+Label.cpython-311.pyc __init__.cpython-311-pytest-9.0.2.pyc
+Legend.cpython-311-pytest-9.0.2.pyc __init__.cpython-311.pyc
+Legend.cpython-311.pyc _imports_.cpython-311-pytest-9.0.2.pyc
+Li.cpython-311-pytest-9.0.2.pyc _imports_.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/dash/labextension:
+dist package.json
+./aylm_env/lib/python3.11/site-packages/dash/labextension/dist:
+dash-jupyterlab.tgz
+./aylm_env/lib/python3.11/site-packages/dash/nbextension:
+__init__.py __pycache__ dash.json main.js
+./aylm_env/lib/python3.11/site-packages/dash/nbextension/__pycache__:
+__init__.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/dash/testing:
+__init__.py composite.py newhooks.py
+__pycache__ consts.py plugin.py
+application_runners.py dash_page.py wait.py
+browser.py errors.py
+./aylm_env/lib/python3.11/site-packages/dash/testing/__pycache__:
+__init__.cpython-311-pytest-9.0.2.pyc
+__init__.cpython-311.pyc
+application_runners.cpython-311-pytest-9.0.2.pyc
+application_runners.cpython-311.pyc
+browser.cpython-311.pyc
+composite.cpython-311.pyc
+consts.cpython-311-pytest-9.0.2.pyc
+consts.cpython-311.pyc
+dash_page.cpython-311.pyc
+errors.cpython-311.pyc
+newhooks.cpython-311.pyc
+plugin.cpython-311-pytest-9.0.2.pyc
+plugin.cpython-311.pyc
+wait.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/dash-4.0.0.dist-info:
+INSTALLER WHEEL top_level.txt
+METADATA entry_points.txt
+RECORD licenses
+./aylm_env/lib/python3.11/site-packages/dash-4.0.0.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/dateutil:
+__init__.py easter.py tz
+__pycache__ parser tzwin.py
+_common.py relativedelta.py utils.py
+_version.py rrule.py zoneinfo
+./aylm_env/lib/python3.11/site-packages/dateutil/__pycache__:
+__init__.cpython-311.pyc easter.cpython-311.pyc
+_common.cpython-311.pyc relativedelta.cpython-311.pyc
+_version.cpython-311.pyc rrule.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/dateutil/parser:
+__init__.py __pycache__ _parser.py isoparser.py
+./aylm_env/lib/python3.11/site-packages/dateutil/parser/__pycache__:
+__init__.cpython-311.pyc isoparser.cpython-311.pyc
+_parser.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/dateutil/tz:
+__init__.py _common.py tz.py
+__pycache__ _factories.py win.py
+./aylm_env/lib/python3.11/site-packages/dateutil/tz/__pycache__:
+__init__.cpython-311.pyc tz.cpython-311.pyc
+_common.cpython-311.pyc win.cpython-311.pyc
+_factories.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/dateutil/zoneinfo:
+__init__.py rebuild.py
+dateutil-zoneinfo.tar.gz
+./aylm_env/lib/python3.11/site-packages/fastjsonschema:
+__init__.py draft04.py exceptions.py ref_resolver.py
+__main__.py draft06.py generator.py version.py
+__pycache__ draft07.py indent.py
+./aylm_env/lib/python3.11/site-packages/fastjsonschema/__pycache__:
+__init__.cpython-311.pyc exceptions.cpython-311.pyc
+__main__.cpython-311.pyc generator.cpython-311.pyc
+draft04.cpython-311.pyc indent.cpython-311.pyc
+draft06.cpython-311.pyc ref_resolver.cpython-311.pyc
+draft07.cpython-311.pyc version.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/fastjsonschema-2.21.2.dist-info:
+INSTALLER RECORD licenses
+METADATA WHEEL top_level.txt
+./aylm_env/lib/python3.11/site-packages/fastjsonschema-2.21.2.dist-info/licenses:
+AUTHORS LICENSE
+./aylm_env/lib/python3.11/site-packages/filelock:
+__init__.py _error.py _unix.py asyncio.py
+__pycache__ _read_write.py _util.py py.typed
+_api.py _soft.py _windows.py version.py
+./aylm_env/lib/python3.11/site-packages/filelock/__pycache__:
+__init__.cpython-311.pyc _unix.cpython-311.pyc
+_api.cpython-311.pyc _util.cpython-311.pyc
+_error.cpython-311.pyc _windows.cpython-311.pyc
+_read_write.cpython-311.pyc asyncio.cpython-311.pyc
+_soft.cpython-311.pyc version.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/filelock-3.24.3.dist-info:
+INSTALLER METADATA RECORD WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/filelock-3.24.3.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/flask:
+__init__.py cli.py helpers.py sessions.py views.py
+__main__.py config.py json signals.py wrappers.py
+__pycache__ ctx.py logging.py templating.py
+app.py debughelpers.py py.typed testing.py
+blueprints.py globals.py sansio typing.py
+./aylm_env/lib/python3.11/site-packages/flask/__pycache__:
+__init__.cpython-311.pyc helpers.cpython-311.pyc
+__main__.cpython-311.pyc logging.cpython-311.pyc
+app.cpython-311.pyc sessions.cpython-311.pyc
+blueprints.cpython-311.pyc signals.cpython-311.pyc
+cli.cpython-311.pyc templating.cpython-311.pyc
+config.cpython-311.pyc testing.cpython-311.pyc
+ctx.cpython-311.pyc typing.cpython-311.pyc
+debughelpers.cpython-311.pyc views.cpython-311.pyc
+globals.cpython-311.pyc wrappers.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/flask/json:
+__init__.py __pycache__ provider.py tag.py
+./aylm_env/lib/python3.11/site-packages/flask/json/__pycache__:
+__init__.cpython-311.pyc tag.cpython-311.pyc
+provider.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/flask/sansio:
+README.md __pycache__ app.py blueprints.py scaffold.py
+./aylm_env/lib/python3.11/site-packages/flask/sansio/__pycache__:
+app.cpython-311.pyc scaffold.cpython-311.pyc
+blueprints.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/flask-3.1.3.dist-info:
+INSTALLER RECORD entry_points.txt
+METADATA WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/flask-3.1.3.dist-info/licenses:
+LICENSE.txt
+./aylm_env/lib/python3.11/site-packages/flatbuffers:
+__init__.py builder.py flexbuffers.py table.py
+__pycache__ compat.py number_types.py util.py
+_version.py encode.py packer.py
+./aylm_env/lib/python3.11/site-packages/flatbuffers/__pycache__:
+__init__.cpython-311.pyc flexbuffers.cpython-311.pyc
+_version.cpython-311.pyc number_types.cpython-311.pyc
+builder.cpython-311.pyc packer.cpython-311.pyc
+compat.cpython-311.pyc table.cpython-311.pyc
+encode.cpython-311.pyc util.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/flatbuffers-25.12.19.dist-info:
+INSTALLER METADATA RECORD WHEEL top_level.txt
+./aylm_env/lib/python3.11/site-packages/fontTools:
+__init__.py config merge svgLib unicodedata
+__main__.py cu2qu misc t1Lib varLib
+afmLib.py designspaceLib mtiLib tfmLib.py voltLib
+agl.py encodings otlLib ttLib
+annotations.py feaLib pens ttx.py
+cffLib fontBuilder.py qu2cu ufoLib
+colorLib help.py subset unicode.py
+./aylm_env/lib/python3.11/site-packages/fontTools/cffLib:
+CFF2ToCFF.py __init__.py transforms.py
+CFFToCFF2.py specializer.py width.py
+./aylm_env/lib/python3.11/site-packages/fontTools/colorLib:
+__init__.py errors.py table_builder.py
+builder.py geometry.py unbuilder.py
+./aylm_env/lib/python3.11/site-packages/fontTools/config:
+__init__.py
+./aylm_env/lib/python3.11/site-packages/fontTools/cu2qu:
+__init__.py cu2qu.cpython-311-darwin.so
+__main__.py cu2qu.py
+benchmark.py errors.py
+cli.py ufo.py
+cu2qu.c
+./aylm_env/lib/python3.11/site-packages/fontTools/designspaceLib:
+__init__.py __main__.py split.py statNames.py types.py
+./aylm_env/lib/python3.11/site-packages/fontTools/encodings:
+MacRoman.py __init__.py
+StandardEncoding.py codecs.py
+./aylm_env/lib/python3.11/site-packages/fontTools/feaLib:
+__init__.py lexer.cpython-311-darwin.so
+__main__.py lexer.py
+ast.py location.py
+builder.py lookupDebugInfo.py
+error.py parser.py
+lexer.c variableScalar.py
+./aylm_env/lib/python3.11/site-packages/fontTools/merge:
+__init__.py base.py layout.py tables.py util.py
+__main__.py cmap.py options.py unicode.py
+./aylm_env/lib/python3.11/site-packages/fontTools/misc:
+__init__.py loggingTools.py
+arrayTools.py macCreatorType.py
+bezierTools.c macRes.py
+bezierTools.cpython-311-darwin.so plistlib
+bezierTools.py psCharStrings.py
+classifyTools.py psLib.py
+cliTools.py psOperators.py
+configTools.py py23.py
+cython.py roundTools.py
+dictTools.py sstruct.py
+eexec.py symfont.py
+encodingTools.py testTools.py
+enumTools.py textTools.py
+etree.py timeTools.py
+filenames.py transform.py
+filesystem treeTools.py
+fixedTools.py vector.py
+intTools.py visitor.py
+iterTools.py xmlReader.py
+lazyTools.py xmlWriter.py
+./aylm_env/lib/python3.11/site-packages/fontTools/misc/filesystem:
+__init__.py _errors.py _path.py _tools.py
+_base.py _info.py _subfs.py _walk.py
+_copy.py _osfs.py _tempfs.py _zipfs.py
+./aylm_env/lib/python3.11/site-packages/fontTools/misc/plistlib:
+__init__.py py.typed
+./aylm_env/lib/python3.11/site-packages/fontTools/mtiLib:
+__init__.py __main__.py
+./aylm_env/lib/python3.11/site-packages/fontTools/otlLib:
+__init__.py error.py optimize
+builder.py maxContextCalc.py
+./aylm_env/lib/python3.11/site-packages/fontTools/otlLib/optimize:
+__init__.py __main__.py gpos.py
+./aylm_env/lib/python3.11/site-packages/fontTools/pens:
+__init__.py pointPen.py
+areaPen.py qtPen.py
+basePen.py qu2cuPen.py
+boundsPen.py quartzPen.py
+cairoPen.py recordingPen.py
+cocoaPen.py reportLabPen.py
+cu2quPen.py reverseContourPen.py
+explicitClosingLinePen.py roundingPen.py
+filterPen.py statisticsPen.py
+freetypePen.py svgPathPen.py
+hashPointPen.py t2CharStringPen.py
+momentsPen.c teePen.py
+momentsPen.cpython-311-darwin.so transformPen.py
+momentsPen.py ttGlyphPen.py
+perimeterPen.py wxPen.py
+pointInsidePen.py
+./aylm_env/lib/python3.11/site-packages/fontTools/qu2cu:
+__init__.py qu2cu.c
+__main__.py qu2cu.cpython-311-darwin.so
+benchmark.py qu2cu.py
+cli.py
+./aylm_env/lib/python3.11/site-packages/fontTools/subset:
+__init__.py __main__.py cff.py svg.py util.py
+./aylm_env/lib/python3.11/site-packages/fontTools/svgLib:
+__init__.py path
+./aylm_env/lib/python3.11/site-packages/fontTools/svgLib/path:
+__init__.py arc.py parser.py shapes.py
+./aylm_env/lib/python3.11/site-packages/fontTools/t1Lib:
+__init__.py
+./aylm_env/lib/python3.11/site-packages/fontTools/ttLib:
+__init__.py scaleUpem.py ttFont.py
+__main__.py sfnt.py ttGlyphSet.py
+macUtils.py standardGlyphOrder.py ttVisitor.py
+removeOverlaps.py tables woff2.py
+reorderGlyphs.py ttCollection.py
+./aylm_env/lib/python3.11/site-packages/fontTools/ttLib/tables:
+B_A_S_E_.py S__i_l_l.py _h_d_m_x.py
+BitmapGlyphMetrics.py T_S_I_B_.py _h_e_a_d.py
+C_B_D_T_.py T_S_I_C_.py _h_h_e_a.py
+C_B_L_C_.py T_S_I_D_.py _h_m_t_x.py
+C_F_F_.py T_S_I_J_.py _k_e_r_n.py
+C_F_F__2.py T_S_I_P_.py _l_c_a_r.py
+C_O_L_R_.py T_S_I_S_.py _l_o_c_a.py
+C_P_A_L_.py T_S_I_V_.py _l_t_a_g.py
+D_S_I_G_.py T_S_I__0.py _m_a_x_p.py
+D__e_b_g.py T_S_I__1.py _m_e_t_a.py
+DefaultTable.py T_S_I__2.py _m_o_r_t.py
+E_B_D_T_.py T_S_I__3.py _m_o_r_x.py
+E_B_L_C_.py T_S_I__5.py _n_a_m_e.py
+F_F_T_M_.py T_T_F_A_.py _o_p_b_d.py
+F__e_a_t.py TupleVariation.py _p_o_s_t.py
+G_D_E_F_.py V_A_R_C_.py _p_r_e_p.py
+G_M_A_P_.py V_D_M_X_.py _p_r_o_p.py
+G_P_K_G_.py V_O_R_G_.py _s_b_i_x.py
+G_P_O_S_.py V_V_A_R_.py _t_r_a_k.py
+G_S_U_B_.py __init__.py _v_h_e_a.py
+G_V_A_R_.py _a_n_k_r.py _v_m_t_x.py
+G__l_a_t.py _a_v_a_r.py asciiTable.py
+G__l_o_c.py _b_s_l_n.py grUtils.py
+H_V_A_R_.py _c_i_d_g.py otBase.py
+J_S_T_F_.py _c_m_a_p.py otConverters.py
+L_T_S_H_.py _c_v_a_r.py otData.py
+M_A_T_H_.py _c_v_t.py otTables.py
+M_E_T_A_.py _f_e_a_t.py otTraverse.py
+M_V_A_R_.py _f_p_g_m.py sbixGlyph.py
+O_S_2f_2.py _f_v_a_r.py sbixStrike.py
+S_I_N_G_.py _g_a_s_p.py table_API_readme.txt
+S_T_A_T_.py _g_c_i_d.py ttProgram.py
+S_V_G_.py _g_l_y_f.py
+S__i_l_f.py _g_v_a_r.py
+./aylm_env/lib/python3.11/site-packages/fontTools/ufoLib:
+__init__.py etree.py kerning.py utils.py
+converters.py filenames.py plistlib.py validators.py
+errors.py glifLib.py pointPen.py
+./aylm_env/lib/python3.11/site-packages/fontTools/unicodedata:
+Blocks.py OTTags.py Scripts.py
+Mirrored.py ScriptExtensions.py __init__.py
+./aylm_env/lib/python3.11/site-packages/fontTools/varLib:
+__init__.py interpolatableTestStartingPoint.py
+__main__.py interpolate_layout.py
+avar iup.c
+avarPlanner.py iup.cpython-311-darwin.so
+builder.py iup.py
+cff.py merger.py
+errors.py models.py
+featureVars.py multiVarStore.py
+hvar.py mutator.py
+instancer mvar.py
+interpolatable.py plot.py
+interpolatableHelpers.py stat.py
+interpolatablePlot.py varStore.py
+interpolatableTestContourOrder.py
+./aylm_env/lib/python3.11/site-packages/fontTools/varLib/avar:
+__init__.py build.py plan.py
+__main__.py map.py unbuild.py
+./aylm_env/lib/python3.11/site-packages/fontTools/varLib/instancer:
+__init__.py __main__.py featureVars.py names.py solver.py
+./aylm_env/lib/python3.11/site-packages/fontTools/voltLib:
+__init__.py ast.py lexer.py voltToFea.py
+__main__.py error.py parser.py
+./aylm_env/lib/python3.11/site-packages/fonttools-4.61.1.dist-info:
+INSTALLER WHEEL top_level.txt
+METADATA entry_points.txt
+RECORD licenses
+./aylm_env/lib/python3.11/site-packages/fonttools-4.61.1.dist-info/licenses:
+LICENSE LICENSE.external
+./aylm_env/lib/python3.11/site-packages/fsspec:
+__init__.py callbacks.py dircache.py implementations spec.py
+_version.py compression.py exceptions.py json.py tests
+archive.py config.py fuse.py mapping.py transaction.py
+asyn.py conftest.py generic.py parquet.py utils.py
+caching.py core.py gui.py registry.py
+./aylm_env/lib/python3.11/site-packages/fsspec/implementations:
+__init__.py dbfs.py libarchive.py
+arrow.py dirfs.py local.py
+asyn_wrapper.py ftp.py memory.py
+cache_mapper.py gist.py reference.py
+cache_metadata.py git.py sftp.py
+cached.py github.py smb.py
+chained.py http.py tar.py
+dask.py http_sync.py webhdfs.py
+data.py jupyter.py zip.py
+./aylm_env/lib/python3.11/site-packages/fsspec/tests:
+abstract
+./aylm_env/lib/python3.11/site-packages/fsspec/tests/abstract:
+__init__.py copy.py mv.py pipe.py
+common.py get.py open.py put.py
+./aylm_env/lib/python3.11/site-packages/fsspec-2026.2.0.dist-info:
+INSTALLER METADATA RECORD WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/fsspec-2026.2.0.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/functorch:
+__init__.py compile einops
+_src dim experimental
+./aylm_env/lib/python3.11/site-packages/functorch/_src:
+__init__.py eager_transforms vmap
+aot_autograd make_functional
+./aylm_env/lib/python3.11/site-packages/functorch/_src/aot_autograd:
+__init__.py
+./aylm_env/lib/python3.11/site-packages/functorch/_src/eager_transforms:
+__init__.py
+./aylm_env/lib/python3.11/site-packages/functorch/_src/make_functional:
+__init__.py
+./aylm_env/lib/python3.11/site-packages/functorch/_src/vmap:
+__init__.py
+./aylm_env/lib/python3.11/site-packages/functorch/compile:
+__init__.py
+./aylm_env/lib/python3.11/site-packages/functorch/dim:
+__init__.py _order.py magic_trace.py
+_dim_entry.py _py_inst_decoder.py op_properties.py
+_enable_all_layers.py _tensor_info.py wrap_type.py
+_getsetitem.py _wrap.py
+./aylm_env/lib/python3.11/site-packages/functorch/einops:
+__init__.py _parsing.py rearrange.py
+./aylm_env/lib/python3.11/site-packages/functorch/experimental:
+__init__.py control_flow.py ops.py
+./aylm_env/lib/python3.11/site-packages/google:
+_upb protobuf
+./aylm_env/lib/python3.11/site-packages/google/_upb:
+_message.abi3.so
+./aylm_env/lib/python3.11/site-packages/google/protobuf:
+__init__.py field_mask_pb2.py source_context_pb2.py
+__pycache__ internal struct_pb2.py
+any.py json_format.py symbol_database.py
+any_pb2.py message.py testdata
+api_pb2.py message_factory.py text_encoding.py
+compiler proto.py text_format.py
+descriptor.py proto_builder.py timestamp.py
+descriptor_database.py proto_json.py timestamp_pb2.py
+descriptor_pb2.py proto_text.py type_pb2.py
+descriptor_pool.py pyext unknown_fields.py
+duration.py reflection.py util
+duration_pb2.py runtime_version.py wrappers_pb2.py
+empty_pb2.py service_reflection.py
+./aylm_env/lib/python3.11/site-packages/google/protobuf/__pycache__:
+__init__.cpython-311.pyc proto_builder.cpython-311.pyc
+any.cpython-311.pyc proto_json.cpython-311.pyc
+any_pb2.cpython-311.pyc proto_text.cpython-311.pyc
+api_pb2.cpython-311.pyc reflection.cpython-311.pyc
+descriptor.cpython-311.pyc runtime_version.cpython-311.pyc
+descriptor_database.cpython-311.pyc service_reflection.cpython-311.pyc
+descriptor_pb2.cpython-311.pyc source_context_pb2.cpython-311.pyc
+descriptor_pool.cpython-311.pyc struct_pb2.cpython-311.pyc
+duration.cpython-311.pyc symbol_database.cpython-311.pyc
+duration_pb2.cpython-311.pyc text_encoding.cpython-311.pyc
+empty_pb2.cpython-311.pyc text_format.cpython-311.pyc
+field_mask_pb2.cpython-311.pyc timestamp.cpython-311.pyc
+json_format.cpython-311.pyc timestamp_pb2.cpython-311.pyc
+message.cpython-311.pyc type_pb2.cpython-311.pyc
+message_factory.cpython-311.pyc unknown_fields.cpython-311.pyc
+proto.cpython-311.pyc wrappers_pb2.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/google/protobuf/compiler:
+__init__.py __pycache__ plugin_pb2.py
+./aylm_env/lib/python3.11/site-packages/google/protobuf/compiler/__pycache__:
+__init__.cpython-311.pyc plugin_pb2.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/google/protobuf/internal:
+__init__.py field_mask.py
+__pycache__ message_listener.py
+api_implementation.py python_edition_defaults.py
+builder.py python_message.py
+containers.py testing_refleaks.py
+decoder.py type_checkers.py
+encoder.py well_known_types.py
+enum_type_wrapper.py wire_format.py
+extension_dict.py
+./aylm_env/lib/python3.11/site-packages/google/protobuf/internal/__pycache__:
+__init__.cpython-311.pyc field_mask.cpython-311.pyc
+api_implementation.cpython-311.pyc message_listener.cpython-311.pyc
+builder.cpython-311.pyc python_edition_defaults.cpython-311.pyc
+containers.cpython-311.pyc python_message.cpython-311.pyc
+decoder.cpython-311.pyc testing_refleaks.cpython-311.pyc
+encoder.cpython-311.pyc type_checkers.cpython-311.pyc
+enum_type_wrapper.cpython-311.pyc well_known_types.cpython-311.pyc
+extension_dict.cpython-311.pyc wire_format.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/google/protobuf/pyext:
+__init__.py __pycache__ cpp_message.py
+./aylm_env/lib/python3.11/site-packages/google/protobuf/pyext/__pycache__:
+__init__.cpython-311.pyc cpp_message.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/google/protobuf/testdata:
+__init__.py __pycache__
+./aylm_env/lib/python3.11/site-packages/google/protobuf/testdata/__pycache__:
+__init__.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/google/protobuf/util:
+__init__.py __pycache__
+./aylm_env/lib/python3.11/site-packages/google/protobuf/util/__pycache__:
+__init__.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/gsplat:
+__init__.py compression exporter.py relocation.py utils.py
+__pycache__ cuda optimizers rendering.py version.py
+_helper.py distributed.py profile.py strategy
+./aylm_env/lib/python3.11/site-packages/gsplat/__pycache__:
+__init__.cpython-311.pyc rendering.cpython-311.pyc
+distributed.cpython-311.pyc utils.cpython-311.pyc
+exporter.cpython-311.pyc version.cpython-311.pyc
+relocation.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/gsplat/compression:
+__init__.py png_compression.py
+__pycache__ sort.py
+./aylm_env/lib/python3.11/site-packages/gsplat/compression/__pycache__:
+__init__.cpython-311.pyc sort.cpython-311.pyc
+png_compression.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda:
+__init__.py _torch_impl.py csrc
+__pycache__ _torch_impl_2dgs.py ext.cpp
+_backend.py _wrapper.py include
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/__pycache__:
+__init__.cpython-311.pyc _torch_impl_2dgs.cpython-311.pyc
+_torch_impl.cpython-311.pyc _wrapper.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc:
+Adam.cpp QuatScaleToCovar.h
+Adam.h QuatScaleToCovarCUDA.cu
+AdamCUDA.cu Rasterization.cpp
+Intersect.cpp Rasterization.h
+Intersect.h RasterizeToIndices2DGS.cu
+IntersectTile.cu RasterizeToIndices3DGS.cu
+Null.cpp RasterizeToPixels2DGSBwd.cu
+Null.h RasterizeToPixels2DGSFwd.cu
+NullCUDA.cu RasterizeToPixels3DGSBwd.cu
+Projection.cpp RasterizeToPixels3DGSFwd.cu
+Projection.h RasterizeToPixelsFromWorld3DGSBwd.cu
+Projection2DGS.cuh RasterizeToPixelsFromWorld3DGSFwd.cu
+Projection2DGSFused.cu Relocation.cpp
+Projection2DGSPacked.cu Relocation.h
+ProjectionEWA3DGSFused.cu RelocationCUDA.cu
+ProjectionEWA3DGSPacked.cu SphericalHarmonics.cpp
+ProjectionEWASimple.cu SphericalHarmonics.h
+ProjectionUT3DGSFused.cu SphericalHarmonicsCUDA.cu
+QuatScaleToCovar.cpp third_party
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party:
+glm
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm:
+CMakeLists.txt copying.txt glm readme.md util
+cmake doc manual.md test
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/cmake:
+cmake_uninstall.cmake.in
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/doc:
+api man.doxy manual manual.pdf theme
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/doc/api:
+a00002.html
+a00002_source.html
+a00005_source.html
+a00008_source.html
+a00011_source.html
+a00014_source.html
+a00017_source.html
+a00020_source.html
+a00023_source.html
+a00026_source.html
+a00029_source.html
+a00032_source.html
+a00035_source.html
+a00038_source.html
+a00041.html
+a00041_source.html
+a00044.html
+a00044_source.html
+a00047.html
+a00047_source.html
+a00050.html
+a00050_source.html
+a00053.html
+a00053_source.html
+a00056.html
+a00056_source.html
+a00059.html
+a00059_source.html
+a00062.html
+a00062_source.html
+a00065.html
+a00065_source.html
+a00068.html
+a00068_source.html
+a00071.html
+a00071_source.html
+a00074.html
+a00074_source.html
+a00077.html
+a00077_source.html
+a00080.html
+a00080_source.html
+a00083.html
+a00083_source.html
+a00086_source.html
+a00089.html
+a00089_source.html
+a00092.html
+a00092_source.html
+a00095.html
+a00095_source.html
+a00098.html
+a00098_source.html
+a00101.html
+a00101_source.html
+a00104.html
+a00104_source.html
+a00107.html
+a00107_source.html
+a00110.html
+a00110_source.html
+a00113.html
+a00113_source.html
+a00116.html
+a00116_source.html
+a00119.html
+a00119_source.html
+a00122.html
+a00122_source.html
+a00125.html
+a00125_source.html
+a00128.html
+a00128_source.html
+a00131.html
+a00131_source.html
+a00134.html
+a00134_source.html
+a00137.html
+a00137_source.html
+a00140.html
+a00140_source.html
+a00143.html
+a00143_source.html
+a00146.html
+a00146_source.html
+a00149.html
+a00149_source.html
+a00152.html
+a00152_source.html
+a00155.html
+a00155_source.html
+a00158.html
+a00158_source.html
+a00161.html
+a00161_source.html
+a00164.html
+a00164_source.html
+a00167.html
+a00167_source.html
+a00170.html
+a00170_source.html
+a00173.html
+a00173_source.html
+a00176.html
+a00176_source.html
+a00179.html
+a00179_source.html
+a00182.html
+a00182_source.html
+a00185.html
+a00185_source.html
+a00188_source.html
+a00191.html
+a00191_source.html
+a00194.html
+a00194_source.html
+a00197.html
+a00197_source.html
+a00200.html
+a00200_source.html
+a00203.html
+a00203_source.html
+a00206.html
+a00206_source.html
+a00209.html
+a00209_source.html
+a00212.html
+a00212_source.html
+a00215.html
+a00215_source.html
+a00218.html
+a00218_source.html
+a00221.html
+a00221_source.html
+a00224.html
+a00224_source.html
+a00227.html
+a00227_source.html
+a00230.html
+a00230_source.html
+a00233.html
+a00233_source.html
+a00236_source.html
+a00239.html
+a00239_source.html
+a00242.html
+a00242_source.html
+a00245.html
+a00245_source.html
+a00248.html
+a00248_source.html
+a00251.html
+a00251_source.html
+a00254.html
+a00254_source.html
+a00260.html
+a00260_source.html
+a00263.html
+a00263_source.html
+a00269.html
+a00269_source.html
+a00272.html
+a00272_source.html
+a00275.html
+a00275_source.html
+a00278.html
+a00278_source.html
+a00281.html
+a00281_source.html
+a00284.html
+a00284_source.html
+a00287.html
+a00287_source.html
+a00290.html
+a00290_source.html
+a00293.html
+a00293_source.html
+a00296.html
+a00296_source.html
+a00299.html
+a00299_source.html
+a00302_source.html
+a00305.html
+a00305_source.html
+a00308.html
+a00308_source.html
+a00311.html
+a00311_source.html
+a00314.html
+a00314_source.html
+a00317.html
+a00317_source.html
+a00320.html
+a00320_source.html
+a00323.html
+a00323_source.html
+a00326.html
+a00326_source.html
+a00329.html
+a00329_source.html
+a00332.html
+a00332_source.html
+a00335.html
+a00335_source.html
+a00338.html
+a00338_source.html
+a00341.html
+a00341_source.html
+a00344.html
+a00344_source.html
+a00347.html
+a00347_source.html
+a00350.html
+a00350_source.html
+a00353.html
+a00353_source.html
+a00356.html
+a00356_source.html
+a00359.html
+a00359_source.html
+a00362.html
+a00362_source.html
+a00365.html
+a00365_source.html
+a00368.html
+a00368_source.html
+a00374.html
+a00374_source.html
+a00377.html
+a00377_source.html
+a00380.html
+a00380_source.html
+a00383.html
+a00383_source.html
+a00386.html
+a00386_source.html
+a00389.html
+a00389_source.html
+a00392.html
+a00392_source.html
+a00395.html
+a00395_source.html
+a00398.html
+a00398_source.html
+a00401.html
+a00401_source.html
+a00404.html
+a00404_source.html
+a00407.html
+a00407_source.html
+a00410.html
+a00410_source.html
+a00413.html
+a00413_source.html
+a00416.html
+a00416_source.html
+a00419.html
+a00419_source.html
+a00422.html
+a00422_source.html
+a00425.html
+a00425_source.html
+a00428.html
+a00428_source.html
+a00431.html
+a00431_source.html
+a00434.html
+a00434_source.html
+a00437.html
+a00437_source.html
+a00440.html
+a00440_source.html
+a00443.html
+a00443_source.html
+a00446.html
+a00446_source.html
+a00449.html
+a00449_source.html
+a00452.html
+a00452_source.html
+a00455.html
+a00455_source.html
+a00458.html
+a00458_source.html
+a00461.html
+a00461_source.html
+a00464.html
+a00464_source.html
+a00467.html
+a00467_source.html
+a00470.html
+a00470_source.html
+a00473.html
+a00473_source.html
+a00476.html
+a00476_source.html
+a00479.html
+a00479_source.html
+a00482.html
+a00482_source.html
+a00485.html
+a00485_source.html
+a00488.html
+a00488_source.html
+a00491.html
+a00491_source.html
+a00494.html
+a00494_source.html
+a00497.html
+a00497_source.html
+a00500.html
+a00500_source.html
+a00503.html
+a00503_source.html
+a00506.html
+a00506_source.html
+a00509.html
+a00509_source.html
+a00512.html
+a00512_source.html
+a00515.html
+a00515_source.html
+a00518.html
+a00518_source.html
+a00521_source.html
+a00524.html
+a00524_source.html
+a00527_source.html
+a00530.html
+a00530_source.html
+a00536.html
+a00536_source.html
+a00539.html
+a00539_source.html
+a00542.html
+a00542_source.html
+a00545.html
+a00545_source.html
+a00548.html
+a00548_source.html
+a00551.html
+a00551_source.html
+a00554.html
+a00554_source.html
+a00560.html
+a00560_source.html
+a00563.html
+a00563_source.html
+a00566.html
+a00566_source.html
+a00572.html
+a00572_source.html
+a00575.html
+a00575_source.html
+a00578.html
+a00578_source.html
+a00581.html
+a00581_source.html
+a00584.html
+a00584_source.html
+a00587.html
+a00587_source.html
+a00590.html
+a00590_source.html
+a00593.html
+a00593_source.html
+a00596.html
+a00596_source.html
+a00599.html
+a00599_source.html
+a00602.html
+a00602_source.html
+a00605.html
+a00605_source.html
+a00608.html
+a00608_source.html
+a00611.html
+a00611_source.html
+a00614.html
+a00614_source.html
+a00617.html
+a00617_source.html
+a00620.html
+a00620_source.html
+a00623.html
+a00623_source.html
+a00626.html
+a00626_source.html
+a00629.html
+a00629_source.html
+a00632.html
+a00632_source.html
+a00635.html
+a00635_source.html
+a00638.html
+a00638_source.html
+a00641.html
+a00641_source.html
+a00644.html
+a00644_source.html
+a00647.html
+a00647_source.html
+a00650.html
+a00650_source.html
+a00653.html
+a00653_source.html
+a00656.html
+a00656_source.html
+a00659.html
+a00659_source.html
+a00662.html
+a00662_source.html
+a00665.html
+a00665_source.html
+a00668.html
+a00668_source.html
+a00671.html
+a00671_source.html
+a00674.html
+a00674_source.html
+a00677.html
+a00677_source.html
+a00680.html
+a00680_source.html
+a00683.html
+a00683_source.html
+a00686.html
+a00686_source.html
+a00689.html
+a00689_source.html
+a00692.html
+a00692_source.html
+a00695.html
+a00695_source.html
+a00698.html
+a00698_source.html
+a00701.html
+a00701_source.html
+a00704.html
+a00704_source.html
+a00707.html
+a00707_source.html
+a00710.html
+a00710_source.html
+a00713.html
+a00713_source.html
+a00716.html
+a00716_source.html
+a00719.html
+a00719_source.html
+a00722.html
+a00722_source.html
+a00725.html
+a00725_source.html
+a00728.html
+a00728_source.html
+a00731.html
+a00731_source.html
+a00734.html
+a00734_source.html
+a00737.html
+a00737_source.html
+a00740.html
+a00740_source.html
+a00743.html
+a00743_source.html
+a00746.html
+a00746_source.html
+a00749.html
+a00749_source.html
+a00752.html
+a00752_source.html
+a00755.html
+a00755_source.html
+a00758.html
+a00758_source.html
+a00761.html
+a00761_source.html
+a00764.html
+a00764_source.html
+a00767.html
+a00767_source.html
+a00770.html
+a00770_source.html
+a00773.html
+a00773_source.html
+a00776.html
+a00776_source.html
+a00779.html
+a00779_source.html
+a00782.html
+a00782_source.html
+a00785.html
+a00785_source.html
+a00788.html
+a00788_source.html
+a00791.html
+a00791_source.html
+a00794.html
+a00794_source.html
+a00797.html
+a00797_source.html
+a00800_source.html
+a00803.html
+a00804.html
+a00805.html
+a00806.html
+a00807.html
+a00808.html
+a00809.html
+a00810.html
+a00811.html
+a00812.html
+a00813.html
+a00814.html
+a00815.html
+a00816.html
+a00817.html
+a00818.html
+a00819.html
+a00820.html
+a00821.html
+a00822.html
+a00823.html
+a00824.html
+a00825.html
+a00826.html
+a00827.html
+a00828.html
+a00829.html
+a00830.html
+a00831.html
+a00832.html
+a00833.html
+a00834.html
+a00835.html
+a00836.html
+a00837.html
+a00838.html
+a00839.html
+a00840.html
+a00841.html
+a00842.html
+a00843.html
+a00844.html
+a00845.html
+a00846.html
+a00847.html
+a00848.html
+a00849.html
+a00850.html
+a00851.html
+a00852.html
+a00853.html
+a00854.html
+a00855.html
+a00856.html
+a00857.html
+a00858.html
+a00859.html
+a00860.html
+a00861.html
+a00862.html
+a00863.html
+a00864.html
+a00865.html
+a00866.html
+a00867.html
+a00868.html
+a00869.html
+a00870.html
+a00871.html
+a00872.html
+a00873.html
+a00874.html
+a00875.html
+a00876.html
+a00877.html
+a00878.html
+a00879.html
+a00880.html
+a00881.html
+a00882.html
+a00883.html
+a00884.html
+a00885.html
+a00886.html
+a00887.html
+a00888.html
+a00889.html
+a00890.html
+a00891.html
+a00892.html
+a00893.html
+a00894.html
+a00895.html
+a00896.html
+a00897.html
+a00898.html
+a00899.html
+a00900.html
+a00901.html
+a00902.html
+a00903.html
+a00904.html
+a00905.html
+a00906.html
+a00907.html
+a00908.html
+a00909.html
+a00910.html
+a00911.html
+a00912.html
+a00913.html
+a00914.html
+a00915.html
+a00916.html
+a00917.html
+a00918.html
+a00919.html
+a00920.html
+a00921.html
+a00922.html
+a00923.html
+a00924.html
+a00925.html
+a00926.html
+a00927.html
+a00928.html
+a00929.html
+a00930.html
+a00931.html
+a00932.html
+a00933.html
+a00934.html
+a00935.html
+a00936.html
+a00937.html
+a00938.html
+a00939.html
+a00940.html
+a00941.html
+a00942.html
+a00943.html
+a00944.html
+a00945.html
+a00946.html
+a00947.html
+a00948.html
+a00949.html
+a00950.html
+a00951.html
+a00952.html
+a00953.html
+a00954.html
+a00955.html
+a00956.html
+a00957.html
+a00958.html
+a00959.html
+a00960.html
+a00961.html
+a00962.html
+a00963.html
+a00964.html
+a00965.html
+a00966.html
+a00967.html
+a00968.html
+a00969.html
+a00970.html
+a00971.html
+a00972.html
+a00973.html
+a00974.html
+a00975.html
+a00976.html
+a00977.html
+a00978.html
+a00979.html
+a00980.html
+a00981.html
+a00982.html
+a00983.html
+a00984.html
+a00985.html
+a01558.html
+a01558_source.html
+a01561.html
+a01561_source.html
+a01564.html
+a01564_source.html
+a01567.html
+a01567_source.html
+a01570.html
+a01570_source.html
+a01573.html
+a01573_source.html
+a01576.html
+a01576_source.html
+a01579.html
+a01579_source.html
+a01582.html
+a01582_source.html
+a01585.html
+a01585_source.html
+a01588.html
+a01588_source.html
+a01591.html
+a01591_source.html
+a01594.html
+a01594_source.html
+a01597.html
+a01597_source.html
+a01600.html
+a01600_source.html
+a01603.html
+a01603_source.html
+a01606.html
+a01606_source.html
+arrowdown.png
+arrowright.png
+bc_s.png
+bdwn.png
+closed.png
+dir_382f4fd018b81be8753cb53c0a41a09a.html
+dir_50f12b6ceb23d7f6adfb377a1ae8b006.html
+dir_628fd60eb37daf5aa9a81e3983c640b7.html
+dir_63ed049134a778d525e06b63afc2c979.html
+dir_885cc87fac2d91e269af0a5a959fa5f6.html
+dir_b11711034def6a4ce452fe9c451dd3d0.html
+dir_b171cecbb853a9ee4caace490047c53f.html
+dir_c98a9ac98258ab9f831b188d66361a70.html
+dir_d9678a6a9012da969e0b059b39347945.html
+dir_fca33f1b5115d46f42c670590789c0d2.html
+doc.png
+doxygen.css
+doxygen.png
+dynsections.js
+files.html
+folderclosed.png
+folderopen.png
+index.html
+jquery.js
+logo-mini.png
+menu.js
+menudata.js
+modules.html
+nav_f.png
+nav_g.png
+nav_h.png
+open.png
+search
+splitbar.png
+sync_off.png
+sync_on.png
+tab_a.png
+tab_b.png
+tab_h.png
+tab_s.png
+tabs.css
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/doc/api/search:
+all_0.html files_4.html functions_d.js
+all_0.js files_4.js functions_e.html
+all_1.html files_5.html functions_e.js
+all_1.js files_5.js functions_f.html
+all_10.html files_6.html functions_f.js
+all_10.js files_6.js groups_0.html
+all_11.html files_7.html groups_0.js
+all_11.js files_7.js groups_1.html
+all_12.html files_8.html groups_1.js
+all_12.js files_8.js groups_2.html
+all_13.html files_9.html groups_2.js
+all_13.js files_9.js groups_3.html
+all_14.html files_a.html groups_3.js
+all_14.js files_a.js groups_4.html
+all_15.html files_b.html groups_4.js
+all_15.js files_b.js groups_5.html
+all_16.html files_c.html groups_5.js
+all_16.js files_c.js groups_6.html
+all_2.html files_d.html groups_6.js
+all_2.js files_d.js groups_7.html
+all_3.html files_e.html groups_7.js
+all_3.js files_e.js groups_8.html
+all_4.html files_f.html groups_8.js
+all_4.js files_f.js groups_9.html
+all_5.html functions_0.html groups_9.js
+all_5.js functions_0.js mag_sel.png
+all_6.html functions_1.html nomatches.html
+all_6.js functions_1.js pages_0.html
+all_7.html functions_10.html pages_0.js
+all_7.js functions_10.js search.css
+all_8.html functions_11.html search.js
+all_8.js functions_11.js search_l.png
+all_9.html functions_12.html search_m.png
+all_9.js functions_12.js search_r.png
+all_a.html functions_13.html searchdata.js
+all_a.js functions_13.js typedefs_0.html
+all_b.html functions_14.html typedefs_0.js
+all_b.js functions_14.js typedefs_1.html
+all_c.html functions_15.html typedefs_1.js
+all_c.js functions_15.js typedefs_2.html
+all_d.html functions_16.html typedefs_2.js
+all_d.js functions_16.js typedefs_3.html
+all_e.html functions_2.html typedefs_3.js
+all_e.js functions_2.js typedefs_4.html
+all_f.html functions_3.html typedefs_4.js
+all_f.js functions_3.js typedefs_5.html
+close.png functions_4.html typedefs_5.js
+files_0.html functions_4.js typedefs_6.html
+files_0.js functions_5.html typedefs_6.js
+files_1.html functions_5.js typedefs_7.html
+files_1.js functions_6.html typedefs_7.js
+files_10.html functions_6.js typedefs_8.html
+files_10.js functions_7.html typedefs_8.js
+files_11.html functions_7.js typedefs_9.html
+files_11.js functions_8.html typedefs_9.js
+files_12.html functions_8.js typedefs_a.html
+files_12.js functions_9.html typedefs_a.js
+files_13.html functions_9.js typedefs_b.html
+files_13.js functions_a.html typedefs_b.js
+files_14.html functions_a.js typedefs_c.html
+files_14.js functions_b.html typedefs_c.js
+files_2.html functions_b.js typedefs_d.html
+files_2.js functions_c.html typedefs_d.js
+files_3.html functions_c.js
+files_3.js functions_d.html
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/doc/manual:
+frontpage1.png random-circularrand.png
+frontpage2.png random-diskrand.png
+g-truc.png random-gaussrand.png
+logo-mini.png random-linearrand.png
+logo.png random-sphericalrand.png
+noise-perlin1.jpg references-cinder.png
+noise-perlin2.jpg references-glsl4book.jpg
+noise-perlin3.jpg references-leosfortune.jpeg
+noise-perlin4.png references-leosfortune2.jpg
+noise-perlin5.png references-opencloth1.png
+noise-perlin6.png references-opencloth3.png
+noise-simplex1.jpg references-outerra1.jpg
+noise-simplex2.jpg references-outerra2.jpg
+noise-simplex3.jpg references-outerra3.jpg
+random-ballrand.png references-outerra4.jpg
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/doc/theme:
+bc_s.png folderopen.png sync_off.png
+bdwn.png logo-mini.png sync_on.png
+closed.png nav_f.png tab_a.png
+doc.png nav_g.png tab_b.png
+doxygen.css nav_h.png tab_h.png
+doxygen.png open.png tab_s.png
+folderclosed.png splitbar.png
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/glm:
+CMakeLists.txt gtc mat4x3.hpp
+common.hpp gtx mat4x4.hpp
+detail integer.hpp matrix.hpp
+exponential.hpp mat2x2.hpp packing.hpp
+ext mat2x3.hpp simd
+ext.hpp mat2x4.hpp trigonometric.hpp
+fwd.hpp mat3x2.hpp vec2.hpp
+geometric.hpp mat3x3.hpp vec3.hpp
+glm.cppm mat3x4.hpp vec4.hpp
+glm.hpp mat4x2.hpp vector_relational.hpp
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/glm/detail:
+_features.hpp type_mat2x2.hpp
+_fixes.hpp type_mat2x2.inl
+_noise.hpp type_mat2x3.hpp
+_swizzle.hpp type_mat2x3.inl
+_swizzle_func.hpp type_mat2x4.hpp
+_vectorize.hpp type_mat2x4.inl
+compute_common.hpp type_mat3x2.hpp
+compute_vector_decl.hpp type_mat3x2.inl
+compute_vector_relational.hpp type_mat3x3.hpp
+func_common.inl type_mat3x3.inl
+func_common_simd.inl type_mat3x4.hpp
+func_exponential.inl type_mat3x4.inl
+func_exponential_simd.inl type_mat4x2.hpp
+func_geometric.inl type_mat4x2.inl
+func_geometric_simd.inl type_mat4x3.hpp
+func_integer.inl type_mat4x3.inl
+func_integer_simd.inl type_mat4x4.hpp
+func_matrix.inl type_mat4x4.inl
+func_matrix_simd.inl type_mat4x4_simd.inl
+func_packing.inl type_quat.hpp
+func_packing_simd.inl type_quat.inl
+func_trigonometric.inl type_quat_simd.inl
+func_trigonometric_simd.inl type_vec1.hpp
+func_vector_relational.inl type_vec1.inl
+func_vector_relational_simd.inl type_vec2.hpp
+glm.cpp type_vec2.inl
+qualifier.hpp type_vec3.hpp
+setup.hpp type_vec3.inl
+type_float.hpp type_vec4.hpp
+type_half.hpp type_vec4.inl
+type_half.inl type_vec_simd.inl
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/glm/ext:
+_matrix_vectorize.hpp quaternion_common.hpp
+matrix_clip_space.hpp quaternion_common.inl
+matrix_clip_space.inl quaternion_common_simd.inl
+matrix_common.hpp quaternion_double.hpp
+matrix_common.inl quaternion_double_precision.hpp
+matrix_double2x2.hpp quaternion_exponential.hpp
+matrix_double2x2_precision.hpp quaternion_exponential.inl
+matrix_double2x3.hpp quaternion_float.hpp
+matrix_double2x3_precision.hpp quaternion_float_precision.hpp
+matrix_double2x4.hpp quaternion_geometric.hpp
+matrix_double2x4_precision.hpp quaternion_geometric.inl
+matrix_double3x2.hpp quaternion_relational.hpp
+matrix_double3x2_precision.hpp quaternion_relational.inl
+matrix_double3x3.hpp quaternion_transform.hpp
+matrix_double3x3_precision.hpp quaternion_transform.inl
+matrix_double3x4.hpp quaternion_trigonometric.hpp
+matrix_double3x4_precision.hpp quaternion_trigonometric.inl
+matrix_double4x2.hpp scalar_common.hpp
+matrix_double4x2_precision.hpp scalar_common.inl
+matrix_double4x3.hpp scalar_constants.hpp
+matrix_double4x3_precision.hpp scalar_constants.inl
+matrix_double4x4.hpp scalar_int_sized.hpp
+matrix_double4x4_precision.hpp scalar_integer.hpp
+matrix_float2x2.hpp scalar_integer.inl
+matrix_float2x2_precision.hpp scalar_packing.hpp
+matrix_float2x3.hpp scalar_packing.inl
+matrix_float2x3_precision.hpp scalar_reciprocal.hpp
+matrix_float2x4.hpp scalar_reciprocal.inl
+matrix_float2x4_precision.hpp scalar_relational.hpp
+matrix_float3x2.hpp scalar_relational.inl
+matrix_float3x2_precision.hpp scalar_uint_sized.hpp
+matrix_float3x3.hpp scalar_ulp.hpp
+matrix_float3x3_precision.hpp scalar_ulp.inl
+matrix_float3x4.hpp vector_bool1.hpp
+matrix_float3x4_precision.hpp vector_bool1_precision.hpp
+matrix_float4x2.hpp vector_bool2.hpp
+matrix_float4x2_precision.hpp vector_bool2_precision.hpp
+matrix_float4x3.hpp vector_bool3.hpp
+matrix_float4x3_precision.hpp vector_bool3_precision.hpp
+matrix_float4x4.hpp vector_bool4.hpp
+matrix_float4x4_precision.hpp vector_bool4_precision.hpp
+matrix_int2x2.hpp vector_common.hpp
+matrix_int2x2_sized.hpp vector_common.inl
+matrix_int2x3.hpp vector_double1.hpp
+matrix_int2x3_sized.hpp vector_double1_precision.hpp
+matrix_int2x4.hpp vector_double2.hpp
+matrix_int2x4_sized.hpp vector_double2_precision.hpp
+matrix_int3x2.hpp vector_double3.hpp
+matrix_int3x2_sized.hpp vector_double3_precision.hpp
+matrix_int3x3.hpp vector_double4.hpp
+matrix_int3x3_sized.hpp vector_double4_precision.hpp
+matrix_int3x4.hpp vector_float1.hpp
+matrix_int3x4_sized.hpp vector_float1_precision.hpp
+matrix_int4x2.hpp vector_float2.hpp
+matrix_int4x2_sized.hpp vector_float2_precision.hpp
+matrix_int4x3.hpp vector_float3.hpp
+matrix_int4x3_sized.hpp vector_float3_precision.hpp
+matrix_int4x4.hpp vector_float4.hpp
+matrix_int4x4_sized.hpp vector_float4_precision.hpp
+matrix_integer.hpp vector_int1.hpp
+matrix_integer.inl vector_int1_sized.hpp
+matrix_projection.hpp vector_int2.hpp
+matrix_projection.inl vector_int2_sized.hpp
+matrix_relational.hpp vector_int3.hpp
+matrix_relational.inl vector_int3_sized.hpp
+matrix_transform.hpp vector_int4.hpp
+matrix_transform.inl vector_int4_sized.hpp
+matrix_uint2x2.hpp vector_integer.hpp
+matrix_uint2x2_sized.hpp vector_integer.inl
+matrix_uint2x3.hpp vector_packing.hpp
+matrix_uint2x3_sized.hpp vector_packing.inl
+matrix_uint2x4.hpp vector_reciprocal.hpp
+matrix_uint2x4_sized.hpp vector_reciprocal.inl
+matrix_uint3x2.hpp vector_relational.hpp
+matrix_uint3x2_sized.hpp vector_relational.inl
+matrix_uint3x3.hpp vector_uint1.hpp
+matrix_uint3x3_sized.hpp vector_uint1_sized.hpp
+matrix_uint3x4.hpp vector_uint2.hpp
+matrix_uint3x4_sized.hpp vector_uint2_sized.hpp
+matrix_uint4x2.hpp vector_uint3.hpp
+matrix_uint4x2_sized.hpp vector_uint3_sized.hpp
+matrix_uint4x3.hpp vector_uint4.hpp
+matrix_uint4x3_sized.hpp vector_uint4_sized.hpp
+matrix_uint4x4.hpp vector_ulp.hpp
+matrix_uint4x4_sized.hpp vector_ulp.inl
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/glm/gtc:
+bitfield.hpp matrix_inverse.hpp reciprocal.hpp
+bitfield.inl matrix_inverse.inl round.hpp
+color_space.hpp matrix_transform.hpp round.inl
+color_space.inl matrix_transform.inl type_aligned.hpp
+constants.hpp noise.hpp type_precision.hpp
+constants.inl noise.inl type_precision.inl
+epsilon.hpp packing.hpp type_ptr.hpp
+epsilon.inl packing.inl type_ptr.inl
+integer.hpp quaternion.hpp ulp.hpp
+integer.inl quaternion.inl ulp.inl
+matrix_access.hpp quaternion_simd.inl vec1.hpp
+matrix_access.inl random.hpp
+matrix_integer.hpp random.inl
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/glm/gtx:
+associated_min_max.hpp matrix_operation.hpp
+associated_min_max.inl matrix_operation.inl
+bit.hpp matrix_query.hpp
+bit.inl matrix_query.inl
+closest_point.hpp matrix_transform_2d.hpp
+closest_point.inl matrix_transform_2d.inl
+color_encoding.hpp mixed_product.hpp
+color_encoding.inl mixed_product.inl
+color_space.hpp norm.hpp
+color_space.inl norm.inl
+color_space_YCoCg.hpp normal.hpp
+color_space_YCoCg.inl normal.inl
+common.hpp normalize_dot.hpp
+common.inl normalize_dot.inl
+compatibility.hpp number_precision.hpp
+compatibility.inl optimum_pow.hpp
+component_wise.hpp optimum_pow.inl
+component_wise.inl orthonormalize.hpp
+dual_quaternion.hpp orthonormalize.inl
+dual_quaternion.inl pca.hpp
+easing.hpp pca.inl
+easing.inl perpendicular.hpp
+euler_angles.hpp perpendicular.inl
+euler_angles.inl polar_coordinates.hpp
+extend.hpp polar_coordinates.inl
+extend.inl projection.hpp
+extended_min_max.hpp projection.inl
+extended_min_max.inl quaternion.hpp
+exterior_product.hpp quaternion.inl
+exterior_product.inl range.hpp
+fast_exponential.hpp raw_data.hpp
+fast_exponential.inl raw_data.inl
+fast_square_root.hpp rotate_normalized_axis.hpp
+fast_square_root.inl rotate_normalized_axis.inl
+fast_trigonometry.hpp rotate_vector.hpp
+fast_trigonometry.inl rotate_vector.inl
+float_normalize.inl scalar_multiplication.hpp
+functions.hpp scalar_relational.hpp
+functions.inl scalar_relational.inl
+gradient_paint.hpp spline.hpp
+gradient_paint.inl spline.inl
+handed_coordinate_space.hpp std_based_type.hpp
+handed_coordinate_space.inl std_based_type.inl
+hash.hpp string_cast.hpp
+hash.inl string_cast.inl
+integer.hpp texture.hpp
+integer.inl texture.inl
+intersect.hpp transform.hpp
+intersect.inl transform.inl
+io.hpp transform2.hpp
+io.inl transform2.inl
+log_base.hpp type_aligned.hpp
+log_base.inl type_aligned.inl
+matrix_cross_product.hpp type_trait.hpp
+matrix_cross_product.inl type_trait.inl
+matrix_decompose.hpp vec_swizzle.hpp
+matrix_decompose.inl vector_angle.hpp
+matrix_factorisation.hpp vector_angle.inl
+matrix_factorisation.inl vector_query.hpp
+matrix_interpolation.hpp vector_query.inl
+matrix_interpolation.inl wrap.hpp
+matrix_major_storage.hpp wrap.inl
+matrix_major_storage.inl
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/glm/simd:
+common.h matrix.h trigonometric.h
+exponential.h neon.h vector_relational.h
+geometric.h packing.h
+integer.h platform.h
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/test:
+CMakeLists.txt cmake ext gtc perf
+bug core glm.cppcheck gtx
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/test/bug:
+CMakeLists.txt bug_ms_vec_static.cpp
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/test/cmake:
+CMakeLists.txt test_find_glm.cpp
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/test/core:
+CMakeLists.txt core_func_trigonometric.cpp
+core_cpp_constexpr.cpp core_func_vector_relational.cpp
+core_cpp_defaulted_ctor.cpp core_setup_force_cxx03.cpp
+core_force_aligned_gentypes.cpp core_setup_force_cxx98.cpp
+core_force_arch_unknown.cpp core_setup_force_cxx_unknown.cpp
+core_force_compiler_unknown.cpp core_setup_force_size_t_length.cpp
+core_force_ctor_init.cpp core_setup_message.cpp
+core_force_depth_zero_to_one.cpp core_setup_platform_unknown.cpp
+core_force_explicit_ctor.cpp core_setup_precision.cpp
+core_force_inline.cpp core_type_aligned.cpp
+core_force_left_handed.cpp core_type_cast.cpp
+core_force_platform_unknown.cpp core_type_ctor.cpp
+core_force_pure.cpp core_type_int.cpp
+core_force_quat_wxyz.cpp core_type_length.cpp
+core_force_size_t_length.cpp core_type_mat2x2.cpp
+core_force_unrestricted_gentype.cpp core_type_mat2x3.cpp
+core_force_xyzw_only.cpp core_type_mat2x4.cpp
+core_func_common.cpp core_type_mat3x2.cpp
+core_func_exponential.cpp core_type_mat3x3.cpp
+core_func_geometric.cpp core_type_mat3x4.cpp
+core_func_integer.cpp core_type_mat4x2.cpp
+core_func_integer_bit_count.cpp core_type_mat4x3.cpp
+core_func_integer_find_lsb.cpp core_type_mat4x4.cpp
+core_func_integer_find_msb.cpp core_type_vec1.cpp
+core_func_matrix.cpp core_type_vec2.cpp
+core_func_noise.cpp core_type_vec3.cpp
+core_func_packing.cpp core_type_vec4.cpp
+core_func_swizzle.cpp
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/test/ext:
+CMakeLists.txt ext_quaternion_trigonometric.cpp
+ext_matrix_clip_space.cpp ext_quaternion_type.cpp
+ext_matrix_common.cpp ext_scalar_common.cpp
+ext_matrix_int2x2_sized.cpp ext_scalar_constants.cpp
+ext_matrix_int2x3_sized.cpp ext_scalar_int_sized.cpp
+ext_matrix_int2x4_sized.cpp ext_scalar_integer.cpp
+ext_matrix_int3x2_sized.cpp ext_scalar_packing.cpp
+ext_matrix_int3x3_sized.cpp ext_scalar_reciprocal.cpp
+ext_matrix_int3x4_sized.cpp ext_scalar_relational.cpp
+ext_matrix_int4x2_sized.cpp ext_scalar_uint_sized.cpp
+ext_matrix_int4x3_sized.cpp ext_scalar_ulp.cpp
+ext_matrix_int4x4_sized.cpp ext_vec1.cpp
+ext_matrix_integer.cpp ext_vector_bool1.cpp
+ext_matrix_projection.cpp ext_vector_common.cpp
+ext_matrix_relational.cpp ext_vector_iec559.cpp
+ext_matrix_transform.cpp ext_vector_int1_sized.cpp
+ext_matrix_uint2x2_sized.cpp ext_vector_int2_sized.cpp
+ext_matrix_uint2x3_sized.cpp ext_vector_int3_sized.cpp
+ext_matrix_uint2x4_sized.cpp ext_vector_int4_sized.cpp
+ext_matrix_uint3x2_sized.cpp ext_vector_integer.cpp
+ext_matrix_uint3x3_sized.cpp ext_vector_integer_sized.cpp
+ext_matrix_uint3x4_sized.cpp ext_vector_packing.cpp
+ext_matrix_uint4x2_sized.cpp ext_vector_reciprocal.cpp
+ext_matrix_uint4x3_sized.cpp ext_vector_relational.cpp
+ext_matrix_uint4x4_sized.cpp ext_vector_uint1_sized.cpp
+ext_quaternion_common.cpp ext_vector_uint2_sized.cpp
+ext_quaternion_exponential.cpp ext_vector_uint3_sized.cpp
+ext_quaternion_geometric.cpp ext_vector_uint4_sized.cpp
+ext_quaternion_relational.cpp ext_vector_ulp.cpp
+ext_quaternion_transform.cpp
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/test/gtc:
+CMakeLists.txt gtc_packing.cpp
+gtc_bitfield.cpp gtc_quaternion.cpp
+gtc_color_space.cpp gtc_random.cpp
+gtc_constants.cpp gtc_reciprocal.cpp
+gtc_epsilon.cpp gtc_round.cpp
+gtc_integer.cpp gtc_type_aligned.cpp
+gtc_matrix_access.cpp gtc_type_precision.cpp
+gtc_matrix_integer.cpp gtc_type_ptr.cpp
+gtc_matrix_inverse.cpp gtc_ulp.cpp
+gtc_matrix_transform.cpp gtc_user_defined_types.cpp
+gtc_noise.cpp gtc_vec1.cpp
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/test/gtx:
+CMakeLists.txt gtx_matrix_interpolation.cpp
+gtx.cpp gtx_matrix_major_storage.cpp
+gtx_associated_min_max.cpp gtx_matrix_operation.cpp
+gtx_closest_point.cpp gtx_matrix_query.cpp
+gtx_color_encoding.cpp gtx_matrix_transform_2d.cpp
+gtx_color_space.cpp gtx_mixed_product.cpp
+gtx_color_space_YCoCg.cpp gtx_norm.cpp
+gtx_common.cpp gtx_normal.cpp
+gtx_compatibility.cpp gtx_normalize_dot.cpp
+gtx_component_wise.cpp gtx_optimum_pow.cpp
+gtx_dual_quaternion.cpp gtx_orthonormalize.cpp
+gtx_easing.cpp gtx_pca.cpp
+gtx_euler_angle.cpp gtx_perpendicular.cpp
+gtx_extend.cpp gtx_polar_coordinates.cpp
+gtx_extended_min_max.cpp gtx_projection.cpp
+gtx_extented_min_max.cpp gtx_quaternion.cpp
+gtx_exterior_product.cpp gtx_random.cpp
+gtx_fast_exponential.cpp gtx_range.cpp
+gtx_fast_square_root.cpp gtx_rotate_normalized_axis.cpp
+gtx_fast_trigonometry.cpp gtx_rotate_vector.cpp
+gtx_functions.cpp gtx_scalar_multiplication.cpp
+gtx_gradient_paint.cpp gtx_scalar_relational.cpp
+gtx_handed_coordinate_space.cpp gtx_simd_mat4.cpp
+gtx_hash.cpp gtx_simd_vec4.cpp
+gtx_int_10_10_10_2.cpp gtx_spline.cpp
+gtx_integer.cpp gtx_string_cast.cpp
+gtx_intersect.cpp gtx_texture.cpp
+gtx_io.cpp gtx_type_aligned.cpp
+gtx_load.cpp gtx_type_trait.cpp
+gtx_log_base.cpp gtx_vec_swizzle.cpp
+gtx_matrix_cross_product.cpp gtx_vector_angle.cpp
+gtx_matrix_decompose.cpp gtx_vector_query.cpp
+gtx_matrix_factorisation.cpp gtx_wrap.cpp
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/test/perf:
+CMakeLists.txt perf_matrix_mul_vector.cpp
+perf_matrix_div.cpp perf_matrix_transpose.cpp
+perf_matrix_inverse.cpp perf_vector_mul_matrix.cpp
+perf_matrix_mul.cpp
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/csrc/third_party/glm/util:
+autoexp.txt glm.natvis
+autoexp.vc2010.dat usertype.dat
+./aylm_env/lib/python3.11/site-packages/gsplat/cuda/include:
+Cameras.cuh Cameras.h Common.h Ops.h Utils.cuh
+./aylm_env/lib/python3.11/site-packages/gsplat/optimizers:
+__init__.py __pycache__ selective_adam.py
+./aylm_env/lib/python3.11/site-packages/gsplat/optimizers/__pycache__:
+__init__.cpython-311.pyc selective_adam.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/gsplat/strategy:
+__init__.py base.py mcmc.py
+__pycache__ default.py ops.py
+./aylm_env/lib/python3.11/site-packages/gsplat/strategy/__pycache__:
+__init__.cpython-311.pyc mcmc.cpython-311.pyc
+base.cpython-311.pyc ops.cpython-311.pyc
+default.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/gsplat-1.5.3.dist-info:
+INSTALLER RECORD licenses
+METADATA WHEEL top_level.txt
+./aylm_env/lib/python3.11/site-packages/gsplat-1.5.3.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/h11:
+__init__.py _headers.py _version.py
+__pycache__ _readers.py _writers.py
+_abnf.py _receivebuffer.py py.typed
+_connection.py _state.py
+_events.py _util.py
+./aylm_env/lib/python3.11/site-packages/h11/__pycache__:
+__init__.cpython-311.pyc _receivebuffer.cpython-311.pyc
+_abnf.cpython-311.pyc _state.cpython-311.pyc
+_connection.cpython-311.pyc _util.cpython-311.pyc
+_events.cpython-311.pyc _version.cpython-311.pyc
+_headers.cpython-311.pyc _writers.cpython-311.pyc
+_readers.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/h11-0.16.0.dist-info:
+INSTALLER RECORD licenses
+METADATA WHEEL top_level.txt
+./aylm_env/lib/python3.11/site-packages/h11-0.16.0.dist-info/licenses:
+LICENSE.txt
+./aylm_env/lib/python3.11/site-packages/hf_xet:
+__init__.py hf_xet.abi3.so
+./aylm_env/lib/python3.11/site-packages/hf_xet-1.2.0.dist-info:
+INSTALLER METADATA RECORD WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/hf_xet-1.2.0.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/httpcore:
+__init__.py _exceptions.py _trace.py
+__pycache__ _models.py _utils.py
+_api.py _ssl.py py.typed
+_async _sync
+_backends _synchronization.py
+./aylm_env/lib/python3.11/site-packages/httpcore/__pycache__:
+__init__.cpython-311.pyc _ssl.cpython-311.pyc
+_api.cpython-311.pyc _synchronization.cpython-311.pyc
+_exceptions.cpython-311.pyc _trace.cpython-311.pyc
+_models.cpython-311.pyc _utils.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/httpcore/_async:
+__init__.py connection_pool.py http_proxy.py
+__pycache__ http11.py interfaces.py
+connection.py http2.py socks_proxy.py
+./aylm_env/lib/python3.11/site-packages/httpcore/_async/__pycache__:
+__init__.cpython-311.pyc http2.cpython-311.pyc
+connection.cpython-311.pyc http_proxy.cpython-311.pyc
+connection_pool.cpython-311.pyc interfaces.cpython-311.pyc
+http11.cpython-311.pyc socks_proxy.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/httpcore/_backends:
+__init__.py anyio.py base.py sync.py
+__pycache__ auto.py mock.py trio.py
+./aylm_env/lib/python3.11/site-packages/httpcore/_backends/__pycache__:
+__init__.cpython-311.pyc mock.cpython-311.pyc
+anyio.cpython-311.pyc sync.cpython-311.pyc
+auto.cpython-311.pyc trio.cpython-311.pyc
+base.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/httpcore/_sync:
+__init__.py connection_pool.py http_proxy.py
+__pycache__ http11.py interfaces.py
+connection.py http2.py socks_proxy.py
+./aylm_env/lib/python3.11/site-packages/httpcore/_sync/__pycache__:
+__init__.cpython-311.pyc http2.cpython-311.pyc
+connection.cpython-311.pyc http_proxy.cpython-311.pyc
+connection_pool.cpython-311.pyc interfaces.cpython-311.pyc
+http11.cpython-311.pyc socks_proxy.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/httpcore-1.0.9.dist-info:
+INSTALLER METADATA RECORD WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/httpcore-1.0.9.dist-info/licenses:
+LICENSE.md
+./aylm_env/lib/python3.11/site-packages/httpx:
+__init__.py _content.py _transports
+__pycache__ _decoders.py _types.py
+__version__.py _exceptions.py _urlparse.py
+_api.py _main.py _urls.py
+_auth.py _models.py _utils.py
+_client.py _multipart.py py.typed
+_config.py _status_codes.py
+./aylm_env/lib/python3.11/site-packages/httpx/__pycache__:
+__init__.cpython-311.pyc _main.cpython-311.pyc
+__version__.cpython-311.pyc _models.cpython-311.pyc
+_api.cpython-311.pyc _multipart.cpython-311.pyc
+_auth.cpython-311.pyc _status_codes.cpython-311.pyc
+_client.cpython-311.pyc _types.cpython-311.pyc
+_config.cpython-311.pyc _urlparse.cpython-311.pyc
+_content.cpython-311.pyc _urls.cpython-311.pyc
+_decoders.cpython-311.pyc _utils.cpython-311.pyc
+_exceptions.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/httpx/_transports:
+__init__.py asgi.py default.py wsgi.py
+__pycache__ base.py mock.py
+./aylm_env/lib/python3.11/site-packages/httpx/_transports/__pycache__:
+__init__.cpython-311.pyc default.cpython-311.pyc
+asgi.cpython-311.pyc mock.cpython-311.pyc
+base.cpython-311.pyc wsgi.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/httpx-0.28.1.dist-info:
+INSTALLER RECORD entry_points.txt
+METADATA WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/httpx-0.28.1.dist-info/licenses:
+LICENSE.md
+./aylm_env/lib/python3.11/site-packages/huggingface_hub:
+__init__.py _tensorboard_logger.py hf_file_system.py
+__pycache__ _upload_large_folder.py hub_mixin.py
+_commit_api.py _webhooks_payload.py inference
+_commit_scheduler.py _webhooks_server.py lfs.py
+_eval_results.py cli py.typed
+_inference_endpoints.py community.py repocard.py
+_jobs_api.py constants.py repocard_data.py
+_local_folder.py dataclasses.py serialization
+_login.py errors.py templates
+_oauth.py fastai_utils.py utils
+_snapshot_download.py file_download.py
+_space_api.py hf_api.py
+./aylm_env/lib/python3.11/site-packages/huggingface_hub/__pycache__:
+__init__.cpython-311.pyc community.cpython-311.pyc
+_commit_api.cpython-311.pyc constants.cpython-311.pyc
+_eval_results.cpython-311.pyc errors.cpython-311.pyc
+_inference_endpoints.cpython-311.pyc file_download.cpython-311.pyc
+_jobs_api.cpython-311.pyc hf_api.cpython-311.pyc
+_local_folder.cpython-311.pyc lfs.cpython-311.pyc
+_space_api.cpython-311.pyc repocard_data.cpython-311.pyc
+_upload_large_folder.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/huggingface_hub/cli:
+__init__.py download.py repo.py
+_cli_utils.py hf.py repo_files.py
+_errors.py inference_endpoints.py skills.py
+auth.py jobs.py spaces.py
+cache.py lfs.py system.py
+collections.py models.py upload.py
+datasets.py papers.py upload_large_folder.py
+./aylm_env/lib/python3.11/site-packages/huggingface_hub/inference:
+__init__.py _common.py _mcp
+_client.py _generated _providers
+./aylm_env/lib/python3.11/site-packages/huggingface_hub/inference/_generated:
+__init__.py _async_client.py types
+./aylm_env/lib/python3.11/site-packages/huggingface_hub/inference/_generated/types:
+__init__.py question_answering.py
+audio_classification.py sentence_similarity.py
+audio_to_audio.py summarization.py
+automatic_speech_recognition.py table_question_answering.py
+base.py text2text_generation.py
+chat_completion.py text_classification.py
+depth_estimation.py text_generation.py
+document_question_answering.py text_to_audio.py
+feature_extraction.py text_to_image.py
+fill_mask.py text_to_speech.py
+image_classification.py text_to_video.py
+image_segmentation.py token_classification.py
+image_text_to_image.py translation.py
+image_text_to_video.py video_classification.py
+image_to_image.py visual_question_answering.py
+image_to_text.py zero_shot_classification.py
+image_to_video.py zero_shot_image_classification.py
+object_detection.py zero_shot_object_detection.py
+./aylm_env/lib/python3.11/site-packages/huggingface_hub/inference/_mcp:
+__init__.py agent.py constants.py types.py
+_cli_hacks.py cli.py mcp_client.py utils.py
+./aylm_env/lib/python3.11/site-packages/huggingface_hub/inference/_providers:
+__init__.py fireworks_ai.py ovhcloud.py
+_common.py groq.py publicai.py
+black_forest_labs.py hf_inference.py replicate.py
+cerebras.py hyperbolic.py sambanova.py
+clarifai.py nebius.py scaleway.py
+cohere.py novita.py together.py
+fal_ai.py nscale.py wavespeed.py
+featherless_ai.py openai.py zai_org.py
+./aylm_env/lib/python3.11/site-packages/huggingface_hub/serialization:
+__init__.py _base.py _dduf.py _torch.py
+./aylm_env/lib/python3.11/site-packages/huggingface_hub/templates:
+datasetcard_template.md modelcard_template.md
+./aylm_env/lib/python3.11/site-packages/huggingface_hub/utils:
+__init__.py _paths.py
+__pycache__ _runtime.py
+_auth.py _safetensors.py
+_cache_assets.py _subprocess.py
+_cache_manager.py _telemetry.py
+_chunk_utils.py _terminal.py
+_datetime.py _typing.py
+_deprecation.py _validators.py
+_dotenv.py _verification.py
+_experimental.py _xet.py
+_fixes.py _xet_progress_reporting.py
+_git_credential.py endpoint_helpers.py
+_headers.py insecure_hashlib.py
+_http.py logging.py
+_lfs.py sha.py
+_pagination.py tqdm.py
+_parsing.py
+./aylm_env/lib/python3.11/site-packages/huggingface_hub/utils/__pycache__:
+__init__.cpython-311.pyc _paths.cpython-311.pyc
+_auth.cpython-311.pyc _runtime.cpython-311.pyc
+_cache_assets.cpython-311.pyc _safetensors.cpython-311.pyc
+_cache_manager.cpython-311.pyc _subprocess.cpython-311.pyc
+_chunk_utils.cpython-311.pyc _telemetry.cpython-311.pyc
+_datetime.cpython-311.pyc _terminal.cpython-311.pyc
+_deprecation.cpython-311.pyc _typing.cpython-311.pyc
+_experimental.cpython-311.pyc _validators.cpython-311.pyc
+_fixes.cpython-311.pyc _verification.cpython-311.pyc
+_git_credential.cpython-311.pyc _xet.cpython-311.pyc
+_headers.cpython-311.pyc endpoint_helpers.cpython-311.pyc
+_http.cpython-311.pyc insecure_hashlib.cpython-311.pyc
+_lfs.cpython-311.pyc logging.cpython-311.pyc
+_pagination.cpython-311.pyc sha.cpython-311.pyc
+_parsing.cpython-311.pyc tqdm.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/huggingface_hub-1.4.1.dist-info:
+INSTALLER WHEEL top_level.txt
+METADATA entry_points.txt
+RECORD licenses
+./aylm_env/lib/python3.11/site-packages/huggingface_hub-1.4.1.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/humanfriendly:
+__init__.py cli.py deprecation.py tables.py tests.py
+__pycache__ compat.py prompts.py terminal text.py
+case.py decorators.py sphinx.py testing.py usage.py
+./aylm_env/lib/python3.11/site-packages/humanfriendly/__pycache__:
+__init__.cpython-311.pyc sphinx.cpython-311.pyc
+case.cpython-311.pyc tables.cpython-311.pyc
+cli.cpython-311.pyc testing.cpython-311.pyc
+compat.cpython-311.pyc tests.cpython-311.pyc
+decorators.cpython-311.pyc text.cpython-311.pyc
+deprecation.cpython-311.pyc usage.cpython-311.pyc
+prompts.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/humanfriendly/terminal:
+__init__.py __pycache__ html.py spinners.py
+./aylm_env/lib/python3.11/site-packages/humanfriendly/terminal/__pycache__:
+__init__.cpython-311.pyc spinners.cpython-311.pyc
+html.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/humanfriendly-10.0.dist-info:
+INSTALLER RECORD top_level.txt
+LICENSE.txt WHEEL
+METADATA entry_points.txt
+./aylm_env/lib/python3.11/site-packages/idna:
+__init__.py codec.py core.py intranges.py py.typed
+__pycache__ compat.py idnadata.py package_data.py uts46data.py
+./aylm_env/lib/python3.11/site-packages/idna/__pycache__:
+__init__.cpython-311.pyc intranges.cpython-311.pyc
+core.cpython-311.pyc package_data.cpython-311.pyc
+idnadata.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/idna-3.11.dist-info:
+INSTALLER METADATA RECORD WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/idna-3.11.dist-info/licenses:
+LICENSE.md
+./aylm_env/lib/python3.11/site-packages/imageio:
+__init__.py config plugins typing.py v3.py
+__main__.py core py.typed v2.py v3.pyi
+__pycache__ freeze.py testing.py v2.pyi
+./aylm_env/lib/python3.11/site-packages/imageio/__pycache__:
+__init__.cpython-311.pyc v2.cpython-311.pyc
+typing.cpython-311.pyc v3.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/imageio/config:
+__init__.py extensions.py plugins.py
+__pycache__ extensions.pyi plugins.pyi
+./aylm_env/lib/python3.11/site-packages/imageio/config/__pycache__:
+__init__.cpython-311.pyc plugins.cpython-311.pyc
+extensions.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/imageio/core:
+__init__.py imopen.pyi
+__pycache__ legacy_plugin_wrapper.py
+fetching.py legacy_plugin_wrapper.pyi
+findlib.py request.py
+format.py request.pyi
+format.pyi util.py
+imopen.py v3_plugin_api.py
+./aylm_env/lib/python3.11/site-packages/imageio/core/__pycache__:
+__init__.cpython-311.pyc legacy_plugin_wrapper.cpython-311.pyc
+fetching.cpython-311.pyc request.cpython-311.pyc
+findlib.cpython-311.pyc util.cpython-311.pyc
+format.cpython-311.pyc v3_plugin_api.cpython-311.pyc
+imopen.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/imageio/plugins:
+__init__.py ffmpeg.py pillow_legacy.py
+__pycache__ fits.py pillowmulti.py
+_bsdf.py freeimage.py pyav.py
+_dicom.py freeimagemulti.py rawpy.py
+_freeimage.py gdal.py simpleitk.py
+_swf.py grab.py spe.py
+_tifffile.py lytro.py swf.py
+bsdf.py npz.py tifffile.py
+dicom.py opencv.py tifffile_v3.py
+example.py pillow.py
+feisem.py pillow_info.py
+./aylm_env/lib/python3.11/site-packages/imageio/plugins/__pycache__:
+__init__.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/imageio-2.37.2.dist-info:
+INSTALLER WHEEL top_level.txt
+METADATA entry_points.txt
+RECORD licenses
+./aylm_env/lib/python3.11/site-packages/imageio-2.37.2.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/imageio_ffmpeg:
+__init__.py _io.py _utils.py
+_definitions.py _parsing.py binaries
+./aylm_env/lib/python3.11/site-packages/imageio_ffmpeg/binaries:
+README.md ffmpeg-macos-aarch64-v7.1
+__init__.py
+./aylm_env/lib/python3.11/site-packages/imageio_ffmpeg-0.6.0.dist-info:
+INSTALLER METADATA WHEEL
+LICENSE RECORD top_level.txt
+./aylm_env/lib/python3.11/site-packages/importlib_metadata:
+__init__.py _collections.py _itertools.py _typing.py py.typed
+__pycache__ _compat.py _meta.py compat
+_adapters.py _functools.py _text.py diagnose.py
+./aylm_env/lib/python3.11/site-packages/importlib_metadata/__pycache__:
+__init__.cpython-311.pyc _itertools.cpython-311.pyc
+_adapters.cpython-311.pyc _meta.cpython-311.pyc
+_collections.cpython-311.pyc _text.cpython-311.pyc
+_compat.cpython-311.pyc _typing.cpython-311.pyc
+_functools.cpython-311.pyc diagnose.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/importlib_metadata/compat:
+__init__.py __pycache__ py311.py py39.py
+./aylm_env/lib/python3.11/site-packages/importlib_metadata/compat/__pycache__:
+__init__.cpython-311.pyc py39.cpython-311.pyc
+py311.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/importlib_metadata-8.7.1.dist-info:
+INSTALLER RECORD licenses
+METADATA WHEEL top_level.txt
+./aylm_env/lib/python3.11/site-packages/importlib_metadata-8.7.1.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/iniconfig:
+__init__.py _parse.py exceptions.py
+__pycache__ _version.py py.typed
+./aylm_env/lib/python3.11/site-packages/iniconfig/__pycache__:
+__init__.cpython-311.pyc _version.cpython-311.pyc
+_parse.cpython-311.pyc exceptions.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/iniconfig-2.3.0.dist-info:
+INSTALLER RECORD licenses
+METADATA WHEEL top_level.txt
+./aylm_env/lib/python3.11/site-packages/iniconfig-2.3.0.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/isort:
+__init__.py comments.py identify.py parse.py sorting.py
+__main__.py core.py io.py place.py stdlibs
+__pycache__ exceptions.py literal.py profiles.py utils.py
+_vendored files.py logo.py py.typed wrap.py
+_version.py format.py main.py sections.py wrap_modes.py
+api.py hooks.py output.py settings.py
+./aylm_env/lib/python3.11/site-packages/isort/__pycache__:
+__init__.cpython-311.pyc main.cpython-311.pyc
+_version.cpython-311.pyc output.cpython-311.pyc
+api.cpython-311.pyc parse.cpython-311.pyc
+comments.cpython-311.pyc place.cpython-311.pyc
+core.cpython-311.pyc profiles.cpython-311.pyc
+exceptions.cpython-311.pyc sections.cpython-311.pyc
+files.cpython-311.pyc settings.cpython-311.pyc
+format.cpython-311.pyc sorting.cpython-311.pyc
+identify.cpython-311.pyc utils.cpython-311.pyc
+io.cpython-311.pyc wrap.cpython-311.pyc
+literal.cpython-311.pyc wrap_modes.cpython-311.pyc
+logo.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/isort/_vendored:
+tomli
+./aylm_env/lib/python3.11/site-packages/isort/_vendored/tomli:
+LICENSE __init__.py _parser.py _re.py py.typed
+./aylm_env/lib/python3.11/site-packages/isort/stdlibs:
+__init__.py py2.py py310.py py313.py py37.py
+__pycache__ py27.py py311.py py314.py py38.py
+all.py py3.py py312.py py36.py py39.py
+./aylm_env/lib/python3.11/site-packages/isort/stdlibs/__pycache__:
+__init__.cpython-311.pyc py312.cpython-311.pyc
+all.cpython-311.pyc py313.cpython-311.pyc
+py2.cpython-311.pyc py314.cpython-311.pyc
+py27.cpython-311.pyc py36.cpython-311.pyc
+py3.cpython-311.pyc py37.cpython-311.pyc
+py310.cpython-311.pyc py38.cpython-311.pyc
+py311.cpython-311.pyc py39.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/isort-8.0.0.dist-info:
+INSTALLER REQUESTED licenses
+METADATA WHEEL
+RECORD entry_points.txt
+./aylm_env/lib/python3.11/site-packages/isort-8.0.0.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/itsdangerous:
+__init__.py _json.py exc.py serializer.py timed.py
+__pycache__ encoding.py py.typed signer.py url_safe.py
+./aylm_env/lib/python3.11/site-packages/itsdangerous/__pycache__:
+__init__.cpython-311.pyc serializer.cpython-311.pyc
+_json.cpython-311.pyc signer.cpython-311.pyc
+encoding.cpython-311.pyc timed.cpython-311.pyc
+exc.cpython-311.pyc url_safe.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/itsdangerous-2.2.0.dist-info:
+INSTALLER LICENSE.txt METADATA RECORD WHEEL
+./aylm_env/lib/python3.11/site-packages/jaxtyping:
+__init__.py _errors.py _pytree_type.py
+__pycache__ _import_hook.py _storage.py
+_array_types.py _indirection.py _typeguard
+_config.py _ipython_extension.py py.typed
+_decorator.py _pytest_plugin.py
+./aylm_env/lib/python3.11/site-packages/jaxtyping/__pycache__:
+__init__.cpython-311-pytest-9.0.2.pyc
+_array_types.cpython-311-pytest-9.0.2.pyc
+_config.cpython-311-pytest-9.0.2.pyc
+_decorator.cpython-311-pytest-9.0.2.pyc
+_errors.cpython-311-pytest-9.0.2.pyc
+_import_hook.cpython-311-pytest-9.0.2.pyc
+_ipython_extension.cpython-311-pytest-9.0.2.pyc
+_pytest_plugin.cpython-311-pytest-9.0.2.pyc
+_storage.cpython-311-pytest-9.0.2.pyc
+./aylm_env/lib/python3.11/site-packages/jaxtyping/_typeguard:
+LICENSE README.md __init__.py
+./aylm_env/lib/python3.11/site-packages/jaxtyping-0.3.9.dist-info:
+INSTALLER RECORD entry_points.txt
+METADATA WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/jaxtyping-0.3.9.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/jinja2:
+__init__.py constants.py filters.py nodes.py tests.py
+__pycache__ debug.py idtracking.py optimizer.py utils.py
+_identifier.py defaults.py lexer.py parser.py visitor.py
+async_utils.py environment.py loaders.py py.typed
+bccache.py exceptions.py meta.py runtime.py
+compiler.py ext.py nativetypes.py sandbox.py
+./aylm_env/lib/python3.11/site-packages/jinja2/__pycache__:
+__init__.cpython-311.pyc lexer.cpython-311.pyc
+_identifier.cpython-311.pyc loaders.cpython-311.pyc
+async_utils.cpython-311.pyc nodes.cpython-311.pyc
+bccache.cpython-311.pyc optimizer.cpython-311.pyc
+compiler.cpython-311.pyc parser.cpython-311.pyc
+defaults.cpython-311.pyc runtime.cpython-311.pyc
+environment.cpython-311.pyc tests.cpython-311.pyc
+exceptions.cpython-311.pyc utils.cpython-311.pyc
+filters.cpython-311.pyc visitor.cpython-311.pyc
+idtracking.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/jinja2-3.1.6.dist-info:
+INSTALLER RECORD entry_points.txt
+METADATA WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/jinja2-3.1.6.dist-info/licenses:
+LICENSE.txt
+./aylm_env/lib/python3.11/site-packages/joblib:
+__init__.py externals
+__pycache__ func_inspect.py
+_cloudpickle_wrapper.py hashing.py
+_dask.py logger.py
+_memmapping_reducer.py memory.py
+_multiprocessing_helpers.py numpy_pickle.py
+_parallel_backends.py numpy_pickle_compat.py
+_store_backends.py numpy_pickle_utils.py
+_utils.py parallel.py
+backports.py pool.py
+compressor.py test
+disk.py testing.py
+executor.py
+./aylm_env/lib/python3.11/site-packages/joblib/__pycache__:
+__init__.cpython-311.pyc
+_cloudpickle_wrapper.cpython-311.pyc
+_dask.cpython-311.pyc
+_memmapping_reducer.cpython-311.pyc
+_multiprocessing_helpers.cpython-311.pyc
+_parallel_backends.cpython-311.pyc
+_store_backends.cpython-311.pyc
+_utils.cpython-311.pyc
+backports.cpython-311.pyc
+compressor.cpython-311.pyc
+disk.cpython-311.pyc
+executor.cpython-311.pyc
+func_inspect.cpython-311.pyc
+hashing.cpython-311.pyc
+logger.cpython-311.pyc
+memory.cpython-311.pyc
+numpy_pickle.cpython-311.pyc
+numpy_pickle_compat.cpython-311.pyc
+numpy_pickle_utils.cpython-311.pyc
+parallel.cpython-311.pyc
+pool.cpython-311.pyc
+testing.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/joblib/externals:
+__init__.py __pycache__ cloudpickle loky
+./aylm_env/lib/python3.11/site-packages/joblib/externals/__pycache__:
+__init__.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/joblib/externals/cloudpickle:
+__init__.py cloudpickle.py
+__pycache__ cloudpickle_fast.py
+./aylm_env/lib/python3.11/site-packages/joblib/externals/cloudpickle/__pycache__:
+__init__.cpython-311.pyc cloudpickle_fast.cpython-311.pyc
+cloudpickle.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/joblib/externals/loky:
+__init__.py backend process_executor.py
+__pycache__ cloudpickle_wrapper.py reusable_executor.py
+_base.py initializers.py
+./aylm_env/lib/python3.11/site-packages/joblib/externals/loky/__pycache__:
+__init__.cpython-311.pyc initializers.cpython-311.pyc
+_base.cpython-311.pyc process_executor.cpython-311.pyc
+cloudpickle_wrapper.cpython-311.pyc reusable_executor.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/joblib/externals/loky/backend:
+__init__.py fork_exec.py reduction.py
+__pycache__ popen_loky_posix.py resource_tracker.py
+_posix_reduction.py popen_loky_win32.py spawn.py
+_win_reduction.py process.py synchronize.py
+context.py queues.py utils.py
+./aylm_env/lib/python3.11/site-packages/joblib/externals/loky/backend/__pycache__:
+__init__.cpython-311.pyc process.cpython-311.pyc
+_posix_reduction.cpython-311.pyc queues.cpython-311.pyc
+_win_reduction.cpython-311.pyc reduction.cpython-311.pyc
+context.cpython-311.pyc resource_tracker.cpython-311.pyc
+fork_exec.cpython-311.pyc spawn.cpython-311.pyc
+popen_loky_posix.cpython-311.pyc synchronize.cpython-311.pyc
+popen_loky_win32.cpython-311.pyc utils.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/joblib/test:
+__init__.py test_memmapping.py
+__pycache__ test_memory.py
+common.py test_memory_async.py
+data test_missing_multiprocessing.py
+test_backports.py test_module.py
+test_cloudpickle_wrapper.py test_numpy_pickle.py
+test_config.py test_numpy_pickle_compat.py
+test_dask.py test_numpy_pickle_utils.py
+test_disk.py test_parallel.py
+test_func_inspect.py test_store_backends.py
+test_func_inspect_special_encoding.py test_testing.py
+test_hashing.py test_utils.py
+test_init.py testutils.py
+test_logger.py
+./aylm_env/lib/python3.11/site-packages/joblib/test/__pycache__:
+__init__.cpython-311.pyc
+common.cpython-311.pyc
+test_backports.cpython-311.pyc
+test_cloudpickle_wrapper.cpython-311.pyc
+test_config.cpython-311.pyc
+test_dask.cpython-311.pyc
+test_disk.cpython-311.pyc
+test_func_inspect.cpython-311.pyc
+test_func_inspect_special_encoding.cpython-311.pyc
+test_hashing.cpython-311.pyc
+test_init.cpython-311.pyc
+test_logger.cpython-311.pyc
+test_memmapping.cpython-311.pyc
+test_memory.cpython-311.pyc
+test_memory_async.cpython-311.pyc
+test_missing_multiprocessing.cpython-311.pyc
+test_module.cpython-311.pyc
+test_numpy_pickle.cpython-311.pyc
+test_numpy_pickle_compat.cpython-311.pyc
+test_numpy_pickle_utils.cpython-311.pyc
+test_parallel.cpython-311.pyc
+test_store_backends.cpython-311.pyc
+test_testing.cpython-311.pyc
+test_utils.cpython-311.pyc
+testutils.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/joblib/test/data:
+__init__.py
+__pycache__
+create_numpy_pickle.py
+joblib_0.10.0_compressed_pickle_py27_np16.gz
+joblib_0.10.0_compressed_pickle_py27_np17.gz
+joblib_0.10.0_compressed_pickle_py33_np18.gz
+joblib_0.10.0_compressed_pickle_py34_np19.gz
+joblib_0.10.0_compressed_pickle_py35_np19.gz
+joblib_0.10.0_pickle_py27_np17.pkl
+joblib_0.10.0_pickle_py27_np17.pkl.bz2
+joblib_0.10.0_pickle_py27_np17.pkl.gzip
+joblib_0.10.0_pickle_py27_np17.pkl.lzma
+joblib_0.10.0_pickle_py27_np17.pkl.xz
+joblib_0.10.0_pickle_py33_np18.pkl
+joblib_0.10.0_pickle_py33_np18.pkl.bz2
+joblib_0.10.0_pickle_py33_np18.pkl.gzip
+joblib_0.10.0_pickle_py33_np18.pkl.lzma
+joblib_0.10.0_pickle_py33_np18.pkl.xz
+joblib_0.10.0_pickle_py34_np19.pkl
+joblib_0.10.0_pickle_py34_np19.pkl.bz2
+joblib_0.10.0_pickle_py34_np19.pkl.gzip
+joblib_0.10.0_pickle_py34_np19.pkl.lzma
+joblib_0.10.0_pickle_py34_np19.pkl.xz
+joblib_0.10.0_pickle_py35_np19.pkl
+joblib_0.10.0_pickle_py35_np19.pkl.bz2
+joblib_0.10.0_pickle_py35_np19.pkl.gzip
+joblib_0.10.0_pickle_py35_np19.pkl.lzma
+joblib_0.10.0_pickle_py35_np19.pkl.xz
+joblib_0.11.0_compressed_pickle_py36_np111.gz
+joblib_0.11.0_pickle_py36_np111.pkl
+joblib_0.11.0_pickle_py36_np111.pkl.bz2
+joblib_0.11.0_pickle_py36_np111.pkl.gzip
+joblib_0.11.0_pickle_py36_np111.pkl.lzma
+joblib_0.11.0_pickle_py36_np111.pkl.xz
+joblib_0.8.4_compressed_pickle_py27_np17.gz
+joblib_0.9.2_compressed_pickle_py27_np16.gz
+joblib_0.9.2_compressed_pickle_py27_np17.gz
+joblib_0.9.2_compressed_pickle_py34_np19.gz
+joblib_0.9.2_compressed_pickle_py35_np19.gz
+joblib_0.9.2_pickle_py27_np16.pkl
+joblib_0.9.2_pickle_py27_np16.pkl_01.npy
+joblib_0.9.2_pickle_py27_np16.pkl_02.npy
+joblib_0.9.2_pickle_py27_np16.pkl_03.npy
+joblib_0.9.2_pickle_py27_np16.pkl_04.npy
+joblib_0.9.2_pickle_py27_np17.pkl
+joblib_0.9.2_pickle_py27_np17.pkl_01.npy
+joblib_0.9.2_pickle_py27_np17.pkl_02.npy
+joblib_0.9.2_pickle_py27_np17.pkl_03.npy
+joblib_0.9.2_pickle_py27_np17.pkl_04.npy
+joblib_0.9.2_pickle_py33_np18.pkl
+joblib_0.9.2_pickle_py33_np18.pkl_01.npy
+joblib_0.9.2_pickle_py33_np18.pkl_02.npy
+joblib_0.9.2_pickle_py33_np18.pkl_03.npy
+joblib_0.9.2_pickle_py33_np18.pkl_04.npy
+joblib_0.9.2_pickle_py34_np19.pkl
+joblib_0.9.2_pickle_py34_np19.pkl_01.npy
+joblib_0.9.2_pickle_py34_np19.pkl_02.npy
+joblib_0.9.2_pickle_py34_np19.pkl_03.npy
+joblib_0.9.2_pickle_py34_np19.pkl_04.npy
+joblib_0.9.2_pickle_py35_np19.pkl
+joblib_0.9.2_pickle_py35_np19.pkl_01.npy
+joblib_0.9.2_pickle_py35_np19.pkl_02.npy
+joblib_0.9.2_pickle_py35_np19.pkl_03.npy
+joblib_0.9.2_pickle_py35_np19.pkl_04.npy
+joblib_0.9.4.dev0_compressed_cache_size_pickle_py35_np19.gz
+joblib_0.9.4.dev0_compressed_cache_size_pickle_py35_np19.gz_01.npy.z
+joblib_0.9.4.dev0_compressed_cache_size_pickle_py35_np19.gz_02.npy.z
+joblib_0.9.4.dev0_compressed_cache_size_pickle_py35_np19.gz_03.npy.z
+./aylm_env/lib/python3.11/site-packages/joblib/test/data/__pycache__:
+__init__.cpython-311.pyc create_numpy_pickle.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/joblib-1.5.3.dist-info:
+INSTALLER RECORD licenses
+METADATA WHEEL top_level.txt
+./aylm_env/lib/python3.11/site-packages/joblib-1.5.3.dist-info/licenses:
+LICENSE.txt
+./aylm_env/lib/python3.11/site-packages/jsonschema:
+__init__.py _legacy_keywords.py cli.py
+__main__.py _types.py exceptions.py
+__pycache__ _typing.py protocols.py
+_format.py _utils.py tests
+_keywords.py benchmarks validators.py
+./aylm_env/lib/python3.11/site-packages/jsonschema/__pycache__:
+__init__.cpython-311.pyc _typing.cpython-311.pyc
+__main__.cpython-311.pyc _utils.cpython-311.pyc
+_format.cpython-311.pyc cli.cpython-311.pyc
+_keywords.cpython-311.pyc exceptions.cpython-311.pyc
+_legacy_keywords.cpython-311.pyc protocols.cpython-311.pyc
+_types.cpython-311.pyc validators.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/jsonschema/benchmarks:
+__init__.py json_schema_test_suite.py
+__pycache__ nested_schemas.py
+const_vs_enum.py subcomponents.py
+contains.py unused_registry.py
+import_benchmark.py useless_applicator_schemas.py
+issue232 useless_keywords.py
+issue232.py validator_creation.py
+./aylm_env/lib/python3.11/site-packages/jsonschema/benchmarks/__pycache__:
+__init__.cpython-311.pyc
+const_vs_enum.cpython-311.pyc
+contains.cpython-311.pyc
+import_benchmark.cpython-311.pyc
+issue232.cpython-311.pyc
+json_schema_test_suite.cpython-311.pyc
+nested_schemas.cpython-311.pyc
+subcomponents.cpython-311.pyc
+unused_registry.cpython-311.pyc
+useless_applicator_schemas.cpython-311.pyc
+useless_keywords.cpython-311.pyc
+validator_creation.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/jsonschema/benchmarks/issue232:
+issue.json
+./aylm_env/lib/python3.11/site-packages/jsonschema/tests:
+__init__.py test_format.py
+__pycache__ test_jsonschema_test_suite.py
+_suite.py test_types.py
+fuzz_validate.py test_utils.py
+test_cli.py test_validators.py
+test_deprecations.py typing
+test_exceptions.py
+./aylm_env/lib/python3.11/site-packages/jsonschema/tests/__pycache__:
+__init__.cpython-311.pyc
+_suite.cpython-311.pyc
+fuzz_validate.cpython-311.pyc
+test_cli.cpython-311.pyc
+test_deprecations.cpython-311.pyc
+test_exceptions.cpython-311.pyc
+test_format.cpython-311.pyc
+test_jsonschema_test_suite.cpython-311.pyc
+test_types.cpython-311.pyc
+test_utils.cpython-311.pyc
+test_validators.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/jsonschema/tests/typing:
+__init__.py
+__pycache__
+test_all_concrete_validators_match_protocol.py
+./aylm_env/lib/python3.11/site-packages/jsonschema/tests/typing/__pycache__:
+__init__.cpython-311.pyc
+test_all_concrete_validators_match_protocol.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/jsonschema-4.26.0.dist-info:
+INSTALLER RECORD entry_points.txt
+METADATA WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/jsonschema-4.26.0.dist-info/licenses:
+COPYING
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications:
+__init__.py __pycache__ _core.py schemas tests
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/__pycache__:
+__init__.cpython-311.pyc _core.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/schemas:
+draft201909 draft3 draft6
+draft202012 draft4 draft7
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/schemas/draft201909:
+metaschema.json vocabularies
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/schemas/draft201909/vocabularies:
+applicator core meta-data
+content format validation
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/schemas/draft202012:
+metaschema.json vocabularies
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/schemas/draft202012/vocabularies:
+applicator format-annotation unevaluated
+content format-assertion validation
+core meta-data
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/schemas/draft3:
+metaschema.json
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/schemas/draft4:
+metaschema.json
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/schemas/draft6:
+metaschema.json
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/schemas/draft7:
+metaschema.json
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/tests:
+__init__.py test_jsonschema_specifications.py
+__pycache__
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications/tests/__pycache__:
+__init__.cpython-311.pyc
+test_jsonschema_specifications.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications-2025.9.1.dist-info:
+INSTALLER METADATA RECORD WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/jsonschema_specifications-2025.9.1.dist-info/licenses:
+COPYING
+./aylm_env/lib/python3.11/site-packages/jupyter_core:
+__init__.py application.py paths.py utils
+__main__.py command.py py.typed version.py
+__pycache__ migrate.py troubleshoot.py
+./aylm_env/lib/python3.11/site-packages/jupyter_core/__pycache__:
+__init__.cpython-311.pyc migrate.cpython-311.pyc
+__main__.cpython-311.pyc paths.cpython-311.pyc
+application.cpython-311.pyc troubleshoot.cpython-311.pyc
+command.cpython-311.pyc version.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/jupyter_core/utils:
+__init__.py __pycache__
+./aylm_env/lib/python3.11/site-packages/jupyter_core/utils/__pycache__:
+__init__.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/jupyter_core-5.9.1.dist-info:
+INSTALLER RECORD entry_points.txt
+METADATA WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/jupyter_core-5.9.1.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/kiwisolver:
+__init__.py _cext.pyi
+__pycache__ exceptions.py
+_cext.cpython-311-darwin.so py.typed
+./aylm_env/lib/python3.11/site-packages/kiwisolver/__pycache__:
+__init__.cpython-311.pyc exceptions.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/kiwisolver-1.4.9.dist-info:
+INSTALLER RECORD licenses
+METADATA WHEEL top_level.txt
+./aylm_env/lib/python3.11/site-packages/kiwisolver-1.4.9.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/librt:
+__init__.pyi strings.cpython-311-darwin.so
+base64.cpython-311-darwin.so strings.pyi
+base64.pyi time.cpython-311-darwin.so
+internal.cpython-311-darwin.so time.pyi
+internal.pyi vecs.cpython-311-darwin.so
+py.typed vecs.pyi
+./aylm_env/lib/python3.11/site-packages/librt-0.8.1.dist-info:
+INSTALLER RECORD licenses
+METADATA WHEEL top_level.txt
+./aylm_env/lib/python3.11/site-packages/librt-0.8.1.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/markdown_it:
+__init__.py parser_block.py ruler.py
+_compat.py parser_core.py rules_block
+_punycode.py parser_inline.py rules_core
+cli port.yaml rules_inline
+common presets token.py
+helpers py.typed tree.py
+main.py renderer.py utils.py
+./aylm_env/lib/python3.11/site-packages/markdown_it/cli:
+__init__.py parse.py
+./aylm_env/lib/python3.11/site-packages/markdown_it/common:
+__init__.py html_blocks.py normalize_url.py
+entities.py html_re.py utils.py
+./aylm_env/lib/python3.11/site-packages/markdown_it/helpers:
+__init__.py parse_link_label.py
+parse_link_destination.py parse_link_title.py
+./aylm_env/lib/python3.11/site-packages/markdown_it/presets:
+__init__.py commonmark.py default.py zero.py
+./aylm_env/lib/python3.11/site-packages/markdown_it/rules_block:
+__init__.py fence.py html_block.py paragraph.py table.py
+blockquote.py heading.py lheading.py reference.py
+code.py hr.py list.py state_block.py
+./aylm_env/lib/python3.11/site-packages/markdown_it/rules_core:
+__init__.py inline.py normalize.py smartquotes.py text_join.py
+block.py linkify.py replacements.py state_core.py
+./aylm_env/lib/python3.11/site-packages/markdown_it/rules_inline:
+__init__.py escape.py newline.py
+autolink.py fragments_join.py state_inline.py
+backticks.py html_inline.py strikethrough.py
+balance_pairs.py image.py text.py
+emphasis.py link.py
+entity.py linkify.py
+./aylm_env/lib/python3.11/site-packages/markdown_it_py-4.0.0.dist-info:
+INSTALLER RECORD entry_points.txt
+METADATA WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/markdown_it_py-4.0.0.dist-info/licenses:
+LICENSE LICENSE.markdown-it
+./aylm_env/lib/python3.11/site-packages/markupsafe:
+__init__.py _speedups.cpython-311-darwin.so
+__pycache__ _speedups.pyi
+_native.py py.typed
+_speedups.c
+./aylm_env/lib/python3.11/site-packages/markupsafe/__pycache__:
+__init__.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/markupsafe-3.0.3.dist-info:
+INSTALLER RECORD licenses
+METADATA WHEEL top_level.txt
+./aylm_env/lib/python3.11/site-packages/markupsafe-3.0.3.dist-info/licenses:
+LICENSE.txt
+./aylm_env/lib/python3.11/site-packages/matplotlib:
+__init__.py figure.py
+__init__.pyi figure.pyi
+__pycache__ font_manager.py
+_afm.py font_manager.pyi
+_animation_data.py ft2font.cpython-311-darwin.so
+_api ft2font.pyi
+_blocking_input.py gridspec.py
+_c_internal_utils.cpython-311-darwin.so gridspec.pyi
+_c_internal_utils.pyi hatch.py
+_cm.py hatch.pyi
+_cm_bivar.py image.py
+_cm_listed.py image.pyi
+_cm_multivar.py inset.py
+_color_data.py inset.pyi
+_color_data.pyi layout_engine.py
+_constrained_layout.py layout_engine.pyi
+_docstring.py legend.py
+_docstring.pyi legend.pyi
+_enums.py legend_handler.py
+_enums.pyi legend_handler.pyi
+_fontconfig_pattern.py lines.py
+_image.cpython-311-darwin.so lines.pyi
+_image.pyi markers.py
+_internal_utils.py markers.pyi
+_layoutgrid.py mathtext.py
+_mathtext.py mathtext.pyi
+_mathtext_data.py mlab.py
+_path.cpython-311-darwin.so mlab.pyi
+_path.pyi mpl-data
+_pylab_helpers.py offsetbox.py
+_pylab_helpers.pyi offsetbox.pyi
+_qhull.cpython-311-darwin.so patches.py
+_qhull.pyi patches.pyi
+_text_helpers.py path.py
+_tight_bbox.py path.pyi
+_tight_layout.py patheffects.py
+_tri.cpython-311-darwin.so patheffects.pyi
+_tri.pyi projections
+_type1font.py py.typed
+_version.py pylab.py
+animation.py pyplot.py
+animation.pyi quiver.py
+artist.py quiver.pyi
+artist.pyi rcsetup.py
+axes rcsetup.pyi
+axis.py sankey.py
+axis.pyi sankey.pyi
+backend_bases.py scale.py
+backend_bases.pyi scale.pyi
+backend_managers.py sphinxext
+backend_managers.pyi spines.py
+backend_tools.py spines.pyi
+backend_tools.pyi stackplot.py
+backends stackplot.pyi
+bezier.py streamplot.py
+bezier.pyi streamplot.pyi
+category.py style
+cbook.py table.py
+cbook.pyi table.pyi
+cm.py testing
+cm.pyi tests
+collections.py texmanager.py
+collections.pyi texmanager.pyi
+colorbar.py text.py
+colorbar.pyi text.pyi
+colorizer.py textpath.py
+colorizer.pyi textpath.pyi
+colors.py ticker.py
+colors.pyi ticker.pyi
+container.py transforms.py
+container.pyi transforms.pyi
+contour.py tri
+contour.pyi typing.py
+dates.py units.py
+dviread.py widgets.py
+dviread.pyi widgets.pyi
+./aylm_env/lib/python3.11/site-packages/matplotlib/__pycache__:
+__init__.cpython-311.pyc contour.cpython-311.pyc
+_afm.cpython-311.pyc dates.cpython-311.pyc
+_blocking_input.cpython-311.pyc dviread.cpython-311.pyc
+_cm.cpython-311.pyc figure.cpython-311.pyc
+_cm_bivar.cpython-311.pyc font_manager.cpython-311.pyc
+_cm_listed.cpython-311.pyc gridspec.cpython-311.pyc
+_cm_multivar.cpython-311.pyc hatch.cpython-311.pyc
+_color_data.cpython-311.pyc image.cpython-311.pyc
+_constrained_layout.cpython-311.pyc inset.cpython-311.pyc
+_docstring.cpython-311.pyc layout_engine.cpython-311.pyc
+_enums.cpython-311.pyc legend.cpython-311.pyc
+_fontconfig_pattern.cpython-311.pyc legend_handler.cpython-311.pyc
+_layoutgrid.cpython-311.pyc lines.cpython-311.pyc
+_mathtext.cpython-311.pyc markers.cpython-311.pyc
+_mathtext_data.cpython-311.pyc mathtext.cpython-311.pyc
+_pylab_helpers.cpython-311.pyc mlab.cpython-311.pyc
+_text_helpers.cpython-311.pyc offsetbox.cpython-311.pyc
+_tight_bbox.cpython-311.pyc patches.cpython-311.pyc
+_tight_layout.cpython-311.pyc path.cpython-311.pyc
+_version.cpython-311.pyc pyplot.cpython-311.pyc
+artist.cpython-311.pyc quiver.cpython-311.pyc
+axis.cpython-311.pyc rcsetup.cpython-311.pyc
+backend_bases.cpython-311.pyc scale.cpython-311.pyc
+backend_managers.cpython-311.pyc spines.cpython-311.pyc
+backend_tools.cpython-311.pyc stackplot.cpython-311.pyc
+bezier.cpython-311.pyc streamplot.cpython-311.pyc
+category.cpython-311.pyc table.cpython-311.pyc
+cbook.cpython-311.pyc texmanager.cpython-311.pyc
+cm.cpython-311.pyc text.cpython-311.pyc
+collections.cpython-311.pyc textpath.cpython-311.pyc
+colorbar.cpython-311.pyc ticker.cpython-311.pyc
+colorizer.cpython-311.pyc transforms.cpython-311.pyc
+colors.cpython-311.pyc units.cpython-311.pyc
+container.cpython-311.pyc widgets.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/matplotlib/_api:
+__init__.py __init__.pyi __pycache__ deprecation.py deprecation.pyi
+./aylm_env/lib/python3.11/site-packages/matplotlib/_api/__pycache__:
+__init__.cpython-311.pyc deprecation.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/matplotlib/axes:
+__init__.py _axes.py _base.pyi
+__init__.pyi _axes.pyi _secondary_axes.py
+__pycache__ _base.py _secondary_axes.pyi
+./aylm_env/lib/python3.11/site-packages/matplotlib/axes/__pycache__:
+__init__.cpython-311.pyc _base.cpython-311.pyc
+_axes.cpython-311.pyc _secondary_axes.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/matplotlib/backends:
+__init__.py backend_pdf.py
+__pycache__ backend_pgf.py
+_backend_agg.cpython-311-darwin.so backend_ps.py
+_backend_agg.pyi backend_qt.py
+_backend_gtk.py backend_qt5.py
+_backend_pdf_ps.py backend_qt5agg.py
+_backend_tk.py backend_qt5cairo.py
+_macosx.cpython-311-darwin.so backend_qtagg.py
+_macosx.pyi backend_qtcairo.py
+_tkagg.cpython-311-darwin.so backend_svg.py
+_tkagg.pyi backend_template.py
+backend_agg.py backend_tkagg.py
+backend_cairo.py backend_tkcairo.py
+backend_gtk3.py backend_webagg.py
+backend_gtk3agg.py backend_webagg_core.py
+backend_gtk3cairo.py backend_wx.py
+backend_gtk4.py backend_wxagg.py
+backend_gtk4agg.py backend_wxcairo.py
+backend_gtk4cairo.py qt_compat.py
+backend_macosx.py qt_editor
+backend_mixed.py registry.py
+backend_nbagg.py web_backend
+./aylm_env/lib/python3.11/site-packages/matplotlib/backends/__pycache__:
+__init__.cpython-311.pyc registry.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/matplotlib/backends/qt_editor:
+__init__.py _formlayout.py figureoptions.py
+./aylm_env/lib/python3.11/site-packages/matplotlib/backends/web_backend:
+all_figures.html js
+css single_figure.html
+ipython_inline_figure.html
+./aylm_env/lib/python3.11/site-packages/matplotlib/backends/web_backend/css:
+boilerplate.css fbm.css mpl.css page.css
+./aylm_env/lib/python3.11/site-packages/matplotlib/backends/web_backend/js:
+mpl.js mpl_tornado.js nbagg_mpl.js
+./aylm_env/lib/python3.11/site-packages/matplotlib/mpl-data:
+fonts kpsewhich.lua plot_directive stylelib
+images matplotlibrc sample_data
+./aylm_env/lib/python3.11/site-packages/matplotlib/mpl-data/fonts:
+afm pdfcorefonts ttf
+./aylm_env/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/afm:
+cmex10.afm pbkdi8a.afm phvbo8an.afm pncri8a.afm putb8a.afm
+cmmi10.afm pbkl8a.afm phvl8a.afm pplb8a.afm putbi8a.afm
+cmr10.afm pbkli8a.afm phvlo8a.afm pplbi8a.afm putr8a.afm
+cmsy10.afm pcrb8a.afm phvr8a.afm pplr8a.afm putri8a.afm
+cmtt10.afm pcrbo8a.afm phvr8an.afm pplri8a.afm pzcmi8a.afm
+pagd8a.afm pcrr8a.afm phvro8a.afm psyr.afm pzdr.afm
+pagdo8a.afm pcrro8a.afm phvro8an.afm ptmb8a.afm
+pagk8a.afm phvb8a.afm pncb8a.afm ptmbi8a.afm
+pagko8a.afm phvb8an.afm pncbi8a.afm ptmr8a.afm
+pbkd8a.afm phvbo8a.afm pncr8a.afm ptmri8a.afm
+./aylm_env/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts:
+Courier-Bold.afm Symbol.afm
+Courier-BoldOblique.afm Times-Bold.afm
+Courier-Oblique.afm Times-BoldItalic.afm
+Courier.afm Times-Italic.afm
+Helvetica-Bold.afm Times-Roman.afm
+Helvetica-BoldOblique.afm ZapfDingbats.afm
+Helvetica-Oblique.afm readme.txt
+Helvetica.afm
+./aylm_env/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf:
+DejaVuSans-Bold.ttf STIXNonUni.ttf
+DejaVuSans-BoldOblique.ttf STIXNonUniBol.ttf
+DejaVuSans-Oblique.ttf STIXNonUniBolIta.ttf
+DejaVuSans.ttf STIXNonUniIta.ttf
+DejaVuSansDisplay.ttf STIXSizFiveSymReg.ttf
+DejaVuSansMono-Bold.ttf STIXSizFourSymBol.ttf
+DejaVuSansMono-BoldOblique.ttf STIXSizFourSymReg.ttf
+DejaVuSansMono-Oblique.ttf STIXSizOneSymBol.ttf
+DejaVuSansMono.ttf STIXSizOneSymReg.ttf
+DejaVuSerif-Bold.ttf STIXSizThreeSymBol.ttf
+DejaVuSerif-BoldItalic.ttf STIXSizThreeSymReg.ttf
+DejaVuSerif-Italic.ttf STIXSizTwoSymBol.ttf
+DejaVuSerif.ttf STIXSizTwoSymReg.ttf
+DejaVuSerifDisplay.ttf cmb10.ttf
+LICENSE_DEJAVU cmex10.ttf
+LICENSE_STIX cmmi10.ttf
+STIXGeneral.ttf cmr10.ttf
+STIXGeneralBol.ttf cmss10.ttf
+STIXGeneralBolIta.ttf cmsy10.ttf
+STIXGeneralItalic.ttf cmtt10.ttf
+./aylm_env/lib/python3.11/site-packages/matplotlib/mpl-data/images:
+back-symbolic.svg home.svg
+back.pdf home_large.png
+back.png matplotlib.pdf
+back.svg matplotlib.png
+back_large.png matplotlib.svg
+filesave-symbolic.svg matplotlib_large.png
+filesave.pdf move-symbolic.svg
+filesave.png move.pdf
+filesave.svg move.png
+filesave_large.png move.svg
+forward-symbolic.svg move_large.png
+forward.pdf qt4_editor_options.pdf
+forward.png qt4_editor_options.png
+forward.svg qt4_editor_options.svg
+forward_large.png qt4_editor_options_large.png
+hand.pdf subplots-symbolic.svg
+hand.png subplots.pdf
+hand.svg subplots.png
+help-symbolic.svg subplots.svg
+help.pdf subplots_large.png
+help.png zoom_to_rect-symbolic.svg
+help.svg zoom_to_rect.pdf
+help_large.png zoom_to_rect.png
+home-symbolic.svg zoom_to_rect.svg
+home.pdf zoom_to_rect_large.png
+home.png
+./aylm_env/lib/python3.11/site-packages/matplotlib/mpl-data/plot_directive:
+plot_directive.css
+./aylm_env/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data:
+Minduka_Present_Blue_Pack.png grace_hopper.jpg
+README.txt jacksboro_fault_dem.npz
+Stocks.csv logo2.png
+axes_grid membrane.dat
+data_x_x2_x3.csv msft.csv
+eeg.dat s1045.ima.gz
+embedding_in_wx3.xrc topobathy.npz
+goog.npz
+./aylm_env/lib/python3.11/site-packages/matplotlib/mpl-data/sample_data/axes_grid:
+bivariate_normal.npy
+./aylm_env/lib/python3.11/site-packages/matplotlib/mpl-data/stylelib:
+Solarize_Light2.mplstyle seaborn-v0_8-dark.mplstyle
+_classic_test_patch.mplstyle seaborn-v0_8-darkgrid.mplstyle
+_mpl-gallery-nogrid.mplstyle seaborn-v0_8-deep.mplstyle
+_mpl-gallery.mplstyle seaborn-v0_8-muted.mplstyle
+bmh.mplstyle seaborn-v0_8-notebook.mplstyle
+classic.mplstyle seaborn-v0_8-paper.mplstyle
+dark_background.mplstyle seaborn-v0_8-pastel.mplstyle
+fast.mplstyle seaborn-v0_8-poster.mplstyle
+fivethirtyeight.mplstyle seaborn-v0_8-talk.mplstyle
+ggplot.mplstyle seaborn-v0_8-ticks.mplstyle
+grayscale.mplstyle seaborn-v0_8-white.mplstyle
+petroff10.mplstyle seaborn-v0_8-whitegrid.mplstyle
+seaborn-v0_8-bright.mplstyle seaborn-v0_8.mplstyle
+seaborn-v0_8-colorblind.mplstyle tableau-colorblind10.mplstyle
+seaborn-v0_8-dark-palette.mplstyle
+./aylm_env/lib/python3.11/site-packages/matplotlib/projections:
+__init__.py __pycache__ geo.pyi polar.pyi
+__init__.pyi geo.py polar.py
+./aylm_env/lib/python3.11/site-packages/matplotlib/projections/__pycache__:
+__init__.cpython-311.pyc polar.cpython-311.pyc
+geo.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/matplotlib/sphinxext:
+__init__.py mathmpl.py roles.py
+figmpl_directive.py plot_directive.py
+./aylm_env/lib/python3.11/site-packages/matplotlib/style:
+__init__.py __pycache__ core.py core.pyi
+./aylm_env/lib/python3.11/site-packages/matplotlib/style/__pycache__:
+__init__.cpython-311.pyc core.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/matplotlib/testing:
+__init__.py compare.py conftest.pyi exceptions.py widgets.pyi
+__init__.pyi compare.pyi decorators.py jpl_units
+_markers.py conftest.py decorators.pyi widgets.py
+./aylm_env/lib/python3.11/site-packages/matplotlib/testing/jpl_units:
+Duration.py StrConverter.py UnitDblFormatter.py
+Epoch.py UnitDbl.py __init__.py
+EpochConverter.py UnitDblConverter.py
+./aylm_env/lib/python3.11/site-packages/matplotlib/tests:
+__init__.py test_font_manager.py
+conftest.py test_fontconfig_pattern.py
+test_afm.py test_ft2font.py
+test_agg.py test_getattr.py
+test_agg_filter.py test_gridspec.py
+test_animation.py test_image.py
+test_api.py test_legend.py
+test_arrow_patches.py test_lines.py
+test_artist.py test_marker.py
+test_axes.py test_mathtext.py
+test_axis.py test_matplotlib.py
+test_backend_bases.py test_mlab.py
+test_backend_cairo.py test_multivariate_colormaps.py
+test_backend_gtk3.py test_offsetbox.py
+test_backend_inline.py test_patches.py
+test_backend_macosx.py test_path.py
+test_backend_nbagg.py test_patheffects.py
+test_backend_pdf.py test_pickle.py
+test_backend_pgf.py test_png.py
+test_backend_ps.py test_polar.py
+test_backend_qt.py test_preprocess_data.py
+test_backend_registry.py test_pyplot.py
+test_backend_svg.py test_quiver.py
+test_backend_template.py test_rcparams.py
+test_backend_tk.py test_sankey.py
+test_backend_tools.py test_scale.py
+test_backend_webagg.py test_simplification.py
+test_backends_interactive.py test_skew.py
+test_basic.py test_sphinxext.py
+test_bbox_tight.py test_spines.py
+test_bezier.py test_streamplot.py
+test_category.py test_style.py
+test_cbook.py test_subplots.py
+test_collections.py test_table.py
+test_colorbar.py test_testing.py
+test_colors.py test_texmanager.py
+test_compare_images.py test_text.py
+test_constrainedlayout.py test_textpath.py
+test_container.py test_ticker.py
+test_contour.py test_tightlayout.py
+test_cycles.py test_transforms.py
+test_dates.py test_triangulation.py
+test_datetime.py test_type1font.py
+test_determinism.py test_units.py
+test_doc.py test_usetex.py
+test_dviread.py test_widgets.py
+test_figure.py
+./aylm_env/lib/python3.11/site-packages/matplotlib/tri:
+__init__.py _trifinder.py _triplot.py
+__pycache__ _trifinder.pyi _triplot.pyi
+_triangulation.py _triinterpolate.py _trirefine.py
+_triangulation.pyi _triinterpolate.pyi _trirefine.pyi
+_tricontour.py _tripcolor.py _tritools.py
+_tricontour.pyi _tripcolor.pyi _tritools.pyi
+./aylm_env/lib/python3.11/site-packages/matplotlib/tri/__pycache__:
+__init__.cpython-311.pyc _tripcolor.cpython-311.pyc
+_triangulation.cpython-311.pyc _triplot.cpython-311.pyc
+_tricontour.cpython-311.pyc _trirefine.cpython-311.pyc
+_trifinder.cpython-311.pyc _tritools.cpython-311.pyc
+_triinterpolate.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/matplotlib-3.10.8.dist-info:
+INSTALLER METADATA REQUESTED
+LICENSE RECORD WHEEL
+./aylm_env/lib/python3.11/site-packages/mdurl:
+__init__.py _encode.py _parse.py py.typed
+_decode.py _format.py _url.py
+./aylm_env/lib/python3.11/site-packages/mdurl-0.1.2.dist-info:
+INSTALLER LICENSE METADATA RECORD WHEEL
+./aylm_env/lib/python3.11/site-packages/ml_dtypes:
+__init__.py _iinfo.py
+__pycache__ _ml_dtypes_ext.cpython-311-darwin.so
+_finfo.py py.typed
+./aylm_env/lib/python3.11/site-packages/ml_dtypes/__pycache__:
+__init__.cpython-311.pyc _iinfo.cpython-311.pyc
+_finfo.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/ml_dtypes-0.5.4.dist-info:
+INSTALLER RECORD licenses
+METADATA WHEEL top_level.txt
+./aylm_env/lib/python3.11/site-packages/ml_dtypes-0.5.4.dist-info/licenses:
+LICENSE LICENSE.eigen
+./aylm_env/lib/python3.11/site-packages/mpl_toolkits:
+axes_grid1 axisartist mplot3d
+./aylm_env/lib/python3.11/site-packages/mpl_toolkits/axes_grid1:
+__init__.py axes_rgb.py parasite_axes.py
+anchored_artists.py axes_size.py tests
+axes_divider.py inset_locator.py
+axes_grid.py mpl_axes.py
+./aylm_env/lib/python3.11/site-packages/mpl_toolkits/axes_grid1/tests:
+__init__.py conftest.py test_axes_grid1.py
+./aylm_env/lib/python3.11/site-packages/mpl_toolkits/axisartist:
+__init__.py floating_axes.py
+angle_helper.py grid_finder.py
+axes_divider.py grid_helper_curvelinear.py
+axis_artist.py parasite_axes.py
+axisline_style.py tests
+axislines.py
+./aylm_env/lib/python3.11/site-packages/mpl_toolkits/axisartist/tests:
+__init__.py test_axislines.py
+conftest.py test_floating_axes.py
+test_angle_helper.py test_grid_finder.py
+test_axis_artist.py test_grid_helper_curvelinear.py
+./aylm_env/lib/python3.11/site-packages/mpl_toolkits/mplot3d:
+__init__.py art3d.py axis3d.py tests
+__pycache__ axes3d.py proj3d.py
+./aylm_env/lib/python3.11/site-packages/mpl_toolkits/mplot3d/__pycache__:
+__init__.cpython-311.pyc axis3d.cpython-311.pyc
+art3d.cpython-311.pyc proj3d.cpython-311.pyc
+axes3d.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mpl_toolkits/mplot3d/tests:
+__init__.py test_art3d.py test_legend3d.py
+conftest.py test_axes3d.py
+./aylm_env/lib/python3.11/site-packages/mpmath:
+__init__.py ctx_mp.py math2.py
+__pycache__ ctx_mp_python.py matrices
+calculus function_docs.py rational.py
+ctx_base.py functions tests
+ctx_fp.py identification.py usertools.py
+ctx_iv.py libmp visualization.py
+./aylm_env/lib/python3.11/site-packages/mpmath/__pycache__:
+__init__.cpython-311.pyc function_docs.cpython-311.pyc
+ctx_base.cpython-311.pyc identification.cpython-311.pyc
+ctx_fp.cpython-311.pyc math2.cpython-311.pyc
+ctx_iv.cpython-311.pyc rational.cpython-311.pyc
+ctx_mp.cpython-311.pyc usertools.cpython-311.pyc
+ctx_mp_python.cpython-311.pyc visualization.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mpmath/calculus:
+__init__.py differentiation.py optimization.py
+__pycache__ extrapolation.py polynomials.py
+approximation.py inverselaplace.py quadrature.py
+calculus.py odes.py
+./aylm_env/lib/python3.11/site-packages/mpmath/calculus/__pycache__:
+__init__.cpython-311.pyc inverselaplace.cpython-311.pyc
+approximation.cpython-311.pyc odes.cpython-311.pyc
+calculus.cpython-311.pyc optimization.cpython-311.pyc
+differentiation.cpython-311.pyc polynomials.cpython-311.pyc
+extrapolation.cpython-311.pyc quadrature.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mpmath/functions:
+__init__.py factorials.py rszeta.py
+__pycache__ functions.py signals.py
+bessel.py hypergeometric.py theta.py
+elliptic.py orthogonal.py zeta.py
+expintegrals.py qfunctions.py zetazeros.py
+./aylm_env/lib/python3.11/site-packages/mpmath/functions/__pycache__:
+__init__.cpython-311.pyc orthogonal.cpython-311.pyc
+bessel.cpython-311.pyc qfunctions.cpython-311.pyc
+elliptic.cpython-311.pyc rszeta.cpython-311.pyc
+expintegrals.cpython-311.pyc signals.cpython-311.pyc
+factorials.cpython-311.pyc theta.cpython-311.pyc
+functions.cpython-311.pyc zeta.cpython-311.pyc
+hypergeometric.cpython-311.pyc zetazeros.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mpmath/libmp:
+__init__.py backend.py libelefun.py libintmath.py libmpf.py
+__pycache__ gammazeta.py libhyper.py libmpc.py libmpi.py
+./aylm_env/lib/python3.11/site-packages/mpmath/libmp/__pycache__:
+__init__.cpython-311.pyc libintmath.cpython-311.pyc
+backend.cpython-311.pyc libmpc.cpython-311.pyc
+gammazeta.cpython-311.pyc libmpf.cpython-311.pyc
+libelefun.cpython-311.pyc libmpi.cpython-311.pyc
+libhyper.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mpmath/matrices:
+__init__.py eigen.py matrices.py
+__pycache__ eigen_symmetric.py
+calculus.py linalg.py
+./aylm_env/lib/python3.11/site-packages/mpmath/matrices/__pycache__:
+__init__.cpython-311.pyc eigen_symmetric.cpython-311.pyc
+calculus.cpython-311.pyc linalg.cpython-311.pyc
+eigen.cpython-311.pyc matrices.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mpmath/tests:
+__init__.py test_eigen_symmetric.py test_mpmath.py
+extratest_gamma.py test_elliptic.py test_ode.py
+extratest_zeta.py test_fp.py test_pickle.py
+runtests.py test_functions.py test_power.py
+test_basic_ops.py test_functions2.py test_quad.py
+test_bitwise.py test_gammazeta.py test_rootfinding.py
+test_calculus.py test_hp.py test_special.py
+test_compatibility.py test_identify.py test_str.py
+test_convert.py test_interval.py test_summation.py
+test_diff.py test_levin.py test_trig.py
+test_division.py test_linalg.py test_visualization.py
+test_eigen.py test_matrices.py torture.py
+./aylm_env/lib/python3.11/site-packages/mpmath-1.3.0.dist-info:
+INSTALLER METADATA WHEEL
+LICENSE RECORD top_level.txt
+./aylm_env/lib/python3.11/site-packages/mypy:
+__init__.cpython-311-darwin.so
+__init__.py
+__main__.py
+__pycache__
+api.cpython-311-darwin.so
+api.py
+applytype.cpython-311-darwin.so
+applytype.py
+argmap.cpython-311-darwin.so
+argmap.py
+binder.cpython-311-darwin.so
+binder.py
+bogus_type.py
+build.cpython-311-darwin.so
+build.py
+cache.cpython-311-darwin.so
+cache.py
+checker.cpython-311-darwin.so
+checker.py
+checker_shared.cpython-311-darwin.so
+checker_shared.py
+checker_state.cpython-311-darwin.so
+checker_state.py
+checkexpr.cpython-311-darwin.so
+checkexpr.py
+checkmember.cpython-311-darwin.so
+checkmember.py
+checkpattern.cpython-311-darwin.so
+checkpattern.py
+checkstrformat.cpython-311-darwin.so
+checkstrformat.py
+config_parser.cpython-311-darwin.so
+config_parser.py
+constant_fold.cpython-311-darwin.so
+constant_fold.py
+constraints.cpython-311-darwin.so
+constraints.py
+copytype.cpython-311-darwin.so
+copytype.py
+defaults.cpython-311-darwin.so
+defaults.py
+dmypy
+dmypy_os.cpython-311-darwin.so
+dmypy_os.py
+dmypy_server.cpython-311-darwin.so
+dmypy_server.py
+dmypy_util.cpython-311-darwin.so
+dmypy_util.py
+erasetype.cpython-311-darwin.so
+erasetype.py
+error_formatter.cpython-311-darwin.so
+error_formatter.py
+errorcodes.cpython-311-darwin.so
+errorcodes.py
+errors.cpython-311-darwin.so
+errors.py
+evalexpr.cpython-311-darwin.so
+evalexpr.py
+expandtype.cpython-311-darwin.so
+expandtype.py
+exportjson.py
+exprtotype.cpython-311-darwin.so
+exprtotype.py
+fastparse.cpython-311-darwin.so
+fastparse.py
+find_sources.cpython-311-darwin.so
+find_sources.py
+fixup.cpython-311-darwin.so
+fixup.py
+freetree.cpython-311-darwin.so
+freetree.py
+fscache.cpython-311-darwin.so
+fscache.py
+fswatcher.cpython-311-darwin.so
+fswatcher.py
+gclogger.cpython-311-darwin.so
+gclogger.py
+git.cpython-311-darwin.so
+git.py
+graph_utils.cpython-311-darwin.so
+graph_utils.py
+indirection.cpython-311-darwin.so
+indirection.py
+infer.cpython-311-darwin.so
+infer.py
+inspections.cpython-311-darwin.so
+inspections.py
+ipc.cpython-311-darwin.so
+ipc.py
+join.cpython-311-darwin.so
+join.py
+literals.cpython-311-darwin.so
+literals.py
+lookup.cpython-311-darwin.so
+lookup.py
+main.cpython-311-darwin.so
+main.py
+maptype.cpython-311-darwin.so
+maptype.py
+meet.cpython-311-darwin.so
+meet.py
+memprofile.cpython-311-darwin.so
+memprofile.py
+message_registry.cpython-311-darwin.so
+message_registry.py
+messages.cpython-311-darwin.so
+messages.py
+metastore.cpython-311-darwin.so
+metastore.py
+mixedtraverser.cpython-311-darwin.so
+mixedtraverser.py
+modulefinder.cpython-311-darwin.so
+modulefinder.py
+moduleinspect.cpython-311-darwin.so
+moduleinspect.py
+mro.cpython-311-darwin.so
+mro.py
+nodes.cpython-311-darwin.so
+nodes.py
+operators.cpython-311-darwin.so
+operators.py
+options.cpython-311-darwin.so
+options.py
+parse.cpython-311-darwin.so
+parse.py
+partially_defined.cpython-311-darwin.so
+partially_defined.py
+patterns.cpython-311-darwin.so
+patterns.py
+plugin.cpython-311-darwin.so
+plugin.py
+plugins
+py.typed
+pyinfo.py
+reachability.cpython-311-darwin.so
+reachability.py
+refinfo.cpython-311-darwin.so
+refinfo.py
+renaming.cpython-311-darwin.so
+renaming.py
+report.cpython-311-darwin.so
+report.py
+scope.cpython-311-darwin.so
+scope.py
+semanal.cpython-311-darwin.so
+semanal.py
+semanal_classprop.cpython-311-darwin.so
+semanal_classprop.py
+semanal_enum.cpython-311-darwin.so
+semanal_enum.py
+semanal_infer.cpython-311-darwin.so
+semanal_infer.py
+semanal_main.cpython-311-darwin.so
+semanal_main.py
+semanal_namedtuple.cpython-311-darwin.so
+semanal_namedtuple.py
+semanal_newtype.cpython-311-darwin.so
+semanal_newtype.py
+semanal_pass1.cpython-311-darwin.so
+semanal_pass1.py
+semanal_shared.cpython-311-darwin.so
+semanal_shared.py
+semanal_typeargs.cpython-311-darwin.so
+semanal_typeargs.py
+semanal_typeddict.cpython-311-darwin.so
+semanal_typeddict.py
+server
+sharedparse.cpython-311-darwin.so
+sharedparse.py
+solve.cpython-311-darwin.so
+solve.py
+split_namespace.py
+state.cpython-311-darwin.so
+state.py
+stats.cpython-311-darwin.so
+stats.py
+strconv.cpython-311-darwin.so
+strconv.py
+stubdoc.py
+stubgen.cpython-311-darwin.so
+stubgen.py
+stubgenc.py
+stubinfo.cpython-311-darwin.so
+stubinfo.py
+stubtest.py
+stubutil.cpython-311-darwin.so
+stubutil.py
+subtypes.cpython-311-darwin.so
+subtypes.py
+suggestions.cpython-311-darwin.so
+suggestions.py
+test
+traverser.cpython-311-darwin.so
+traverser.py
+treetransform.cpython-311-darwin.so
+treetransform.py
+tvar_scope.cpython-311-darwin.so
+tvar_scope.py
+type_visitor.cpython-311-darwin.so
+type_visitor.py
+typeanal.cpython-311-darwin.so
+typeanal.py
+typeops.cpython-311-darwin.so
+typeops.py
+types.cpython-311-darwin.so
+types.py
+types_utils.cpython-311-darwin.so
+types_utils.py
+typeshed
+typestate.cpython-311-darwin.so
+typestate.py
+typetraverser.cpython-311-darwin.so
+typetraverser.py
+typevars.cpython-311-darwin.so
+typevars.py
+typevartuples.cpython-311-darwin.so
+typevartuples.py
+util.cpython-311-darwin.so
+util.py
+version.py
+visitor.cpython-311-darwin.so
+visitor.py
+xml
+./aylm_env/lib/python3.11/site-packages/mypy/__pycache__:
+__init__.cpython-311.pyc modulefinder.cpython-311.pyc
+__main__.cpython-311.pyc moduleinspect.cpython-311.pyc
+api.cpython-311.pyc mro.cpython-311.pyc
+applytype.cpython-311.pyc nodes.cpython-311.pyc
+argmap.cpython-311.pyc operators.cpython-311.pyc
+binder.cpython-311.pyc options.cpython-311.pyc
+bogus_type.cpython-311.pyc parse.cpython-311.pyc
+build.cpython-311.pyc partially_defined.cpython-311.pyc
+cache.cpython-311.pyc patterns.cpython-311.pyc
+checker.cpython-311.pyc plugin.cpython-311.pyc
+checker_shared.cpython-311.pyc pyinfo.cpython-311.pyc
+checker_state.cpython-311.pyc reachability.cpython-311.pyc
+checkexpr.cpython-311.pyc refinfo.cpython-311.pyc
+checkmember.cpython-311.pyc renaming.cpython-311.pyc
+checkpattern.cpython-311.pyc report.cpython-311.pyc
+checkstrformat.cpython-311.pyc scope.cpython-311.pyc
+config_parser.cpython-311.pyc semanal.cpython-311.pyc
+constant_fold.cpython-311.pyc semanal_classprop.cpython-311.pyc
+constraints.cpython-311.pyc semanal_enum.cpython-311.pyc
+copytype.cpython-311.pyc semanal_infer.cpython-311.pyc
+defaults.cpython-311.pyc semanal_main.cpython-311.pyc
+dmypy_os.cpython-311.pyc semanal_namedtuple.cpython-311.pyc
+dmypy_server.cpython-311.pyc semanal_newtype.cpython-311.pyc
+dmypy_util.cpython-311.pyc semanal_pass1.cpython-311.pyc
+erasetype.cpython-311.pyc semanal_shared.cpython-311.pyc
+error_formatter.cpython-311.pyc semanal_typeargs.cpython-311.pyc
+errorcodes.cpython-311.pyc semanal_typeddict.cpython-311.pyc
+errors.cpython-311.pyc sharedparse.cpython-311.pyc
+evalexpr.cpython-311.pyc solve.cpython-311.pyc
+expandtype.cpython-311.pyc split_namespace.cpython-311.pyc
+exportjson.cpython-311.pyc state.cpython-311.pyc
+exprtotype.cpython-311.pyc stats.cpython-311.pyc
+fastparse.cpython-311.pyc strconv.cpython-311.pyc
+find_sources.cpython-311.pyc stubdoc.cpython-311.pyc
+fixup.cpython-311.pyc stubgen.cpython-311.pyc
+freetree.cpython-311.pyc stubgenc.cpython-311.pyc
+fscache.cpython-311.pyc stubinfo.cpython-311.pyc
+fswatcher.cpython-311.pyc stubtest.cpython-311.pyc
+gclogger.cpython-311.pyc stubutil.cpython-311.pyc
+git.cpython-311.pyc subtypes.cpython-311.pyc
+graph_utils.cpython-311.pyc suggestions.cpython-311.pyc
+indirection.cpython-311.pyc traverser.cpython-311.pyc
+infer.cpython-311.pyc treetransform.cpython-311.pyc
+inspections.cpython-311.pyc tvar_scope.cpython-311.pyc
+ipc.cpython-311.pyc type_visitor.cpython-311.pyc
+join.cpython-311.pyc typeanal.cpython-311.pyc
+literals.cpython-311.pyc typeops.cpython-311.pyc
+lookup.cpython-311.pyc types.cpython-311.pyc
+main.cpython-311.pyc types_utils.cpython-311.pyc
+maptype.cpython-311.pyc typestate.cpython-311.pyc
+meet.cpython-311.pyc typetraverser.cpython-311.pyc
+memprofile.cpython-311.pyc typevars.cpython-311.pyc
+message_registry.cpython-311.pyc typevartuples.cpython-311.pyc
+messages.cpython-311.pyc util.cpython-311.pyc
+metastore.cpython-311.pyc version.cpython-311.pyc
+mixedtraverser.cpython-311.pyc visitor.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypy/dmypy:
+__init__.cpython-311-darwin.so __pycache__
+__init__.py client.cpython-311-darwin.so
+__main__.py client.py
+./aylm_env/lib/python3.11/site-packages/mypy/dmypy/__pycache__:
+__init__.cpython-311.pyc client.cpython-311.pyc
+__main__.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypy/plugins:
+__init__.cpython-311-darwin.so dataclasses.py
+__init__.py default.cpython-311-darwin.so
+__pycache__ default.py
+attrs.cpython-311-darwin.so enums.cpython-311-darwin.so
+attrs.py enums.py
+common.cpython-311-darwin.so functools.cpython-311-darwin.so
+common.py functools.py
+constants.cpython-311-darwin.so proper_plugin.cpython-311-darwin.so
+constants.py proper_plugin.py
+ctypes.cpython-311-darwin.so singledispatch.cpython-311-darwin.so
+ctypes.py singledispatch.py
+dataclasses.cpython-311-darwin.so
+./aylm_env/lib/python3.11/site-packages/mypy/plugins/__pycache__:
+__init__.cpython-311.pyc default.cpython-311.pyc
+attrs.cpython-311.pyc enums.cpython-311.pyc
+common.cpython-311.pyc functools.cpython-311.pyc
+constants.cpython-311.pyc proper_plugin.cpython-311.pyc
+ctypes.cpython-311.pyc singledispatch.cpython-311.pyc
+dataclasses.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypy/server:
+__init__.cpython-311-darwin.so mergecheck.py
+__init__.py objgraph.cpython-311-darwin.so
+__pycache__ objgraph.py
+astdiff.cpython-311-darwin.so subexpr.cpython-311-darwin.so
+astdiff.py subexpr.py
+astmerge.cpython-311-darwin.so target.cpython-311-darwin.so
+astmerge.py target.py
+aststrip.cpython-311-darwin.so trigger.cpython-311-darwin.so
+aststrip.py trigger.py
+deps.cpython-311-darwin.so update.cpython-311-darwin.so
+deps.py update.py
+mergecheck.cpython-311-darwin.so
+./aylm_env/lib/python3.11/site-packages/mypy/server/__pycache__:
+__init__.cpython-311.pyc objgraph.cpython-311.pyc
+astdiff.cpython-311.pyc subexpr.cpython-311.pyc
+astmerge.cpython-311.pyc target.cpython-311.pyc
+aststrip.cpython-311.pyc trigger.cpython-311.pyc
+deps.cpython-311.pyc update.cpython-311.pyc
+mergecheck.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypy/test:
+__init__.py testinfer.py
+__pycache__ testipc.py
+config.py testmerge.py
+data.py testmodulefinder.py
+helpers.py testmypyc.py
+meta testoutput.py
+test_config_parser.py testparse.py
+test_find_sources.py testpep561.py
+test_ref_info.py testpythoneval.py
+testapi.py testreports.py
+testargs.py testsemanal.py
+testcheck.py testsolve.py
+testcmdline.py teststubgen.py
+testconstraints.py teststubinfo.py
+testdaemon.py teststubtest.py
+testdeps.py testsubtypes.py
+testdiff.py testtransform.py
+testerrorstream.py testtypegen.py
+testexportjson.py testtypes.py
+testfinegrained.py testutil.py
+testfinegrainedcache.py typefixture.py
+testformatter.py update_data.py
+testfscache.py visitors.cpython-311-darwin.so
+testgraph.py visitors.py
+./aylm_env/lib/python3.11/site-packages/mypy/test/__pycache__:
+__init__.cpython-311.pyc testipc.cpython-311.pyc
+config.cpython-311.pyc testmerge.cpython-311.pyc
+data.cpython-311.pyc testmodulefinder.cpython-311.pyc
+helpers.cpython-311.pyc testmypyc.cpython-311.pyc
+test_config_parser.cpython-311.pyc testoutput.cpython-311.pyc
+test_find_sources.cpython-311.pyc testparse.cpython-311.pyc
+test_ref_info.cpython-311.pyc testpep561.cpython-311.pyc
+testapi.cpython-311.pyc testpythoneval.cpython-311.pyc
+testargs.cpython-311.pyc testreports.cpython-311.pyc
+testcheck.cpython-311.pyc testsemanal.cpython-311.pyc
+testcmdline.cpython-311.pyc testsolve.cpython-311.pyc
+testconstraints.cpython-311.pyc teststubgen.cpython-311.pyc
+testdaemon.cpython-311.pyc teststubinfo.cpython-311.pyc
+testdeps.cpython-311.pyc teststubtest.cpython-311.pyc
+testdiff.cpython-311.pyc testsubtypes.cpython-311.pyc
+testerrorstream.cpython-311.pyc testtransform.cpython-311.pyc
+testexportjson.cpython-311.pyc testtypegen.cpython-311.pyc
+testfinegrained.cpython-311.pyc testtypes.cpython-311.pyc
+testfinegrainedcache.cpython-311.pyc testutil.cpython-311.pyc
+testformatter.cpython-311.pyc typefixture.cpython-311.pyc
+testfscache.cpython-311.pyc update_data.cpython-311.pyc
+testgraph.cpython-311.pyc visitors.cpython-311.pyc
+testinfer.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypy/test/meta:
+__init__.py _pytest.py test_parse_data.py
+__pycache__ test_diff_helper.py test_update_data.py
+./aylm_env/lib/python3.11/site-packages/mypy/test/meta/__pycache__:
+__init__.cpython-311.pyc test_parse_data.cpython-311.pyc
+_pytest.cpython-311.pyc test_update_data.cpython-311.pyc
+test_diff_helper.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed:
+LICENSE stdlib stubs
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib:
+VERSIONS imp.pyi
+__future__.pyi importlib
+__main__.pyi inspect.pyi
+_ast.pyi io.pyi
+_asyncio.pyi ipaddress.pyi
+_bisect.pyi itertools.pyi
+_blake2.pyi json
+_bootlocale.pyi keyword.pyi
+_bz2.pyi lib2to3
+_codecs.pyi linecache.pyi
+_collections_abc.pyi locale.pyi
+_compat_pickle.pyi logging
+_compression.pyi lzma.pyi
+_contextvars.pyi mailbox.pyi
+_csv.pyi mailcap.pyi
+_ctypes.pyi marshal.pyi
+_curses.pyi math.pyi
+_curses_panel.pyi mimetypes.pyi
+_dbm.pyi mmap.pyi
+_decimal.pyi modulefinder.pyi
+_frozen_importlib.pyi msilib
+_frozen_importlib_external.pyi msvcrt.pyi
+_gdbm.pyi multiprocessing
+_hashlib.pyi netrc.pyi
+_heapq.pyi nis.pyi
+_imp.pyi nntplib.pyi
+_interpchannels.pyi nt.pyi
+_interpqueues.pyi ntpath.pyi
+_interpreters.pyi nturl2path.pyi
+_io.pyi numbers.pyi
+_json.pyi opcode.pyi
+_locale.pyi operator.pyi
+_lsprof.pyi optparse.pyi
+_lzma.pyi os
+_markupbase.pyi ossaudiodev.pyi
+_msi.pyi parser.pyi
+_multibytecodec.pyi pathlib
+_operator.pyi pdb.pyi
+_osx_support.pyi pickle.pyi
+_pickle.pyi pickletools.pyi
+_posixsubprocess.pyi pipes.pyi
+_py_abc.pyi pkgutil.pyi
+_pydecimal.pyi platform.pyi
+_queue.pyi plistlib.pyi
+_random.pyi poplib.pyi
+_sitebuiltins.pyi posix.pyi
+_socket.pyi posixpath.pyi
+_sqlite3.pyi pprint.pyi
+_ssl.pyi profile.pyi
+_stat.pyi pstats.pyi
+_struct.pyi pty.pyi
+_thread.pyi pwd.pyi
+_threading_local.pyi py_compile.pyi
+_tkinter.pyi pyclbr.pyi
+_tracemalloc.pyi pydoc.pyi
+_typeshed pydoc_data
+_warnings.pyi pyexpat
+_weakref.pyi queue.pyi
+_weakrefset.pyi quopri.pyi
+_winapi.pyi random.pyi
+_zstd.pyi re.pyi
+abc.pyi readline.pyi
+aifc.pyi reprlib.pyi
+annotationlib.pyi resource.pyi
+antigravity.pyi rlcompleter.pyi
+argparse.pyi runpy.pyi
+array.pyi sched.pyi
+ast.pyi secrets.pyi
+asynchat.pyi select.pyi
+asyncio selectors.pyi
+asyncore.pyi shelve.pyi
+atexit.pyi shlex.pyi
+audioop.pyi shutil.pyi
+base64.pyi signal.pyi
+bdb.pyi site.pyi
+binascii.pyi smtpd.pyi
+binhex.pyi smtplib.pyi
+bisect.pyi sndhdr.pyi
+builtins.pyi socket.pyi
+bz2.pyi socketserver.pyi
+cProfile.pyi spwd.pyi
+calendar.pyi sqlite3
+cgi.pyi sre_compile.pyi
+cgitb.pyi sre_constants.pyi
+chunk.pyi sre_parse.pyi
+cmath.pyi ssl.pyi
+cmd.pyi stat.pyi
+code.pyi statistics.pyi
+codecs.pyi string
+codeop.pyi stringprep.pyi
+collections struct.pyi
+colorsys.pyi subprocess.pyi
+compileall.pyi sunau.pyi
+compression symbol.pyi
+concurrent symtable.pyi
+configparser.pyi sys
+contextlib.pyi sysconfig.pyi
+contextvars.pyi syslog.pyi
+copy.pyi tabnanny.pyi
+copyreg.pyi tarfile.pyi
+crypt.pyi telnetlib.pyi
+csv.pyi tempfile.pyi
+ctypes termios.pyi
+curses textwrap.pyi
+dataclasses.pyi this.pyi
+datetime.pyi threading.pyi
+dbm time.pyi
+decimal.pyi timeit.pyi
+difflib.pyi tkinter
+dis.pyi token.pyi
+distutils tokenize.pyi
+doctest.pyi tomllib.pyi
+email trace.pyi
+encodings traceback.pyi
+ensurepip tracemalloc.pyi
+enum.pyi tty.pyi
+errno.pyi turtle.pyi
+faulthandler.pyi types.pyi
+fcntl.pyi typing.pyi
+filecmp.pyi typing_extensions.pyi
+fileinput.pyi unicodedata.pyi
+fnmatch.pyi unittest
+formatter.pyi urllib
+fractions.pyi uu.pyi
+ftplib.pyi uuid.pyi
+functools.pyi venv
+gc.pyi warnings.pyi
+genericpath.pyi wave.pyi
+getopt.pyi weakref.pyi
+getpass.pyi webbrowser.pyi
+gettext.pyi winreg.pyi
+glob.pyi winsound.pyi
+graphlib.pyi wsgiref
+grp.pyi xdrlib.pyi
+gzip.pyi xml
+hashlib.pyi xmlrpc
+heapq.pyi xxlimited.pyi
+hmac.pyi zipapp.pyi
+html zipfile
+http zipimport.pyi
+imaplib.pyi zlib.pyi
+imghdr.pyi zoneinfo
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/_typeshed:
+__init__.pyi importlib.pyi
+_type_checker_internals.pyi wsgi.pyi
+dbapi.pyi xml.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/asyncio:
+__init__.pyi locks.pyi taskgroups.pyi
+base_events.pyi log.pyi tasks.pyi
+base_futures.pyi mixins.pyi threads.pyi
+base_subprocess.pyi proactor_events.pyi timeouts.pyi
+base_tasks.pyi protocols.pyi tools.pyi
+constants.pyi queues.pyi transports.pyi
+coroutines.pyi runners.pyi trsock.pyi
+events.pyi selector_events.pyi unix_events.pyi
+exceptions.pyi sslproto.pyi windows_events.pyi
+format_helpers.pyi staggered.pyi windows_utils.pyi
+futures.pyi streams.pyi
+graph.pyi subprocess.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/collections:
+__init__.pyi abc.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/compression:
+__init__.pyi bz2.pyi lzma.pyi zstd
+_common gzip.pyi zlib.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/compression/_common:
+__init__.pyi _streams.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/compression/zstd:
+__init__.pyi _zstdfile.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/concurrent:
+__init__.pyi futures interpreters
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/concurrent/futures:
+__init__.pyi _base.pyi interpreter.pyi process.pyi thread.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/concurrent/interpreters:
+__init__.pyi _crossinterp.pyi _queues.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/ctypes:
+__init__.pyi _endian.pyi macholib util.pyi wintypes.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/ctypes/macholib:
+__init__.pyi dyld.pyi dylib.pyi framework.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/curses:
+__init__.pyi ascii.pyi has_key.pyi panel.pyi textpad.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/dbm:
+__init__.pyi dumb.pyi gnu.pyi ndbm.pyi sqlite3.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/distutils:
+__init__.pyi cygwinccompiler.pyi filelist.pyi
+_msvccompiler.pyi debug.pyi log.pyi
+archive_util.pyi dep_util.pyi msvccompiler.pyi
+bcppcompiler.pyi dir_util.pyi spawn.pyi
+ccompiler.pyi dist.pyi sysconfig.pyi
+cmd.pyi errors.pyi text_file.pyi
+command extension.pyi unixccompiler.pyi
+config.pyi fancy_getopt.pyi util.pyi
+core.pyi file_util.pyi version.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/distutils/command:
+__init__.pyi build_clib.pyi install_data.pyi
+bdist.pyi build_ext.pyi install_egg_info.pyi
+bdist_dumb.pyi build_py.pyi install_headers.pyi
+bdist_msi.pyi build_scripts.pyi install_lib.pyi
+bdist_packager.pyi check.pyi install_scripts.pyi
+bdist_rpm.pyi clean.pyi register.pyi
+bdist_wininst.pyi config.pyi sdist.pyi
+build.pyi install.pyi upload.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/email:
+__init__.pyi header.pyi
+_header_value_parser.pyi headerregistry.pyi
+_policybase.pyi iterators.pyi
+base64mime.pyi message.pyi
+charset.pyi mime
+contentmanager.pyi parser.pyi
+encoders.pyi policy.pyi
+errors.pyi quoprimime.pyi
+feedparser.pyi utils.pyi
+generator.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/email/mime:
+__init__.pyi base.pyi multipart.pyi
+application.pyi image.pyi nonmultipart.pyi
+audio.pyi message.pyi text.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/encodings:
+__init__.pyi cp866.pyi koi8_r.pyi
+aliases.pyi cp869.pyi koi8_t.pyi
+ascii.pyi cp874.pyi koi8_u.pyi
+base64_codec.pyi cp875.pyi kz1048.pyi
+big5.pyi cp932.pyi latin_1.pyi
+big5hkscs.pyi cp949.pyi mac_arabic.pyi
+bz2_codec.pyi cp950.pyi mac_croatian.pyi
+charmap.pyi euc_jis_2004.pyi mac_cyrillic.pyi
+cp037.pyi euc_jisx0213.pyi mac_farsi.pyi
+cp1006.pyi euc_jp.pyi mac_greek.pyi
+cp1026.pyi euc_kr.pyi mac_iceland.pyi
+cp1125.pyi gb18030.pyi mac_latin2.pyi
+cp1140.pyi gb2312.pyi mac_roman.pyi
+cp1250.pyi gbk.pyi mac_romanian.pyi
+cp1251.pyi hex_codec.pyi mac_turkish.pyi
+cp1252.pyi hp_roman8.pyi mbcs.pyi
+cp1253.pyi hz.pyi oem.pyi
+cp1254.pyi idna.pyi palmos.pyi
+cp1255.pyi iso2022_jp.pyi ptcp154.pyi
+cp1256.pyi iso2022_jp_1.pyi punycode.pyi
+cp1257.pyi iso2022_jp_2.pyi quopri_codec.pyi
+cp1258.pyi iso2022_jp_2004.pyi raw_unicode_escape.pyi
+cp273.pyi iso2022_jp_3.pyi rot_13.pyi
+cp424.pyi iso2022_jp_ext.pyi shift_jis.pyi
+cp437.pyi iso2022_kr.pyi shift_jis_2004.pyi
+cp500.pyi iso8859_1.pyi shift_jisx0213.pyi
+cp720.pyi iso8859_10.pyi tis_620.pyi
+cp737.pyi iso8859_11.pyi undefined.pyi
+cp775.pyi iso8859_13.pyi unicode_escape.pyi
+cp850.pyi iso8859_14.pyi utf_16.pyi
+cp852.pyi iso8859_15.pyi utf_16_be.pyi
+cp855.pyi iso8859_16.pyi utf_16_le.pyi
+cp856.pyi iso8859_2.pyi utf_32.pyi
+cp857.pyi iso8859_3.pyi utf_32_be.pyi
+cp858.pyi iso8859_4.pyi utf_32_le.pyi
+cp860.pyi iso8859_5.pyi utf_7.pyi
+cp861.pyi iso8859_6.pyi utf_8.pyi
+cp862.pyi iso8859_7.pyi utf_8_sig.pyi
+cp863.pyi iso8859_8.pyi uu_codec.pyi
+cp864.pyi iso8859_9.pyi zlib_codec.pyi
+cp865.pyi johab.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/ensurepip:
+__init__.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/html:
+__init__.pyi entities.pyi parser.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/http:
+__init__.pyi client.pyi cookiejar.pyi cookies.pyi server.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/importlib:
+__init__.pyi abc.pyi resources
+_abc.pyi machinery.pyi simple.pyi
+_bootstrap.pyi metadata util.pyi
+_bootstrap_external.pyi readers.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/importlib/metadata:
+__init__.pyi _meta.pyi diagnose.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/importlib/resources:
+__init__.pyi _functional.pyi readers.pyi
+_common.pyi abc.pyi simple.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/json:
+__init__.pyi decoder.pyi encoder.pyi scanner.pyi tool.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/lib2to3:
+__init__.pyi fixer_base.pyi main.pyi pygram.pyi refactor.pyi
+btm_matcher.pyi fixes pgen2 pytree.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/lib2to3/fixes:
+__init__.pyi fix_methodattrs.pyi
+fix_apply.pyi fix_ne.pyi
+fix_asserts.pyi fix_next.pyi
+fix_basestring.pyi fix_nonzero.pyi
+fix_buffer.pyi fix_numliterals.pyi
+fix_dict.pyi fix_operator.pyi
+fix_except.pyi fix_paren.pyi
+fix_exec.pyi fix_print.pyi
+fix_execfile.pyi fix_raise.pyi
+fix_exitfunc.pyi fix_raw_input.pyi
+fix_filter.pyi fix_reduce.pyi
+fix_funcattrs.pyi fix_reload.pyi
+fix_future.pyi fix_renames.pyi
+fix_getcwdu.pyi fix_repr.pyi
+fix_has_key.pyi fix_set_literal.pyi
+fix_idioms.pyi fix_standarderror.pyi
+fix_import.pyi fix_sys_exc.pyi
+fix_imports.pyi fix_throw.pyi
+fix_imports2.pyi fix_tuple_params.pyi
+fix_input.pyi fix_types.pyi
+fix_intern.pyi fix_unicode.pyi
+fix_isinstance.pyi fix_urllib.pyi
+fix_itertools.pyi fix_ws_comma.pyi
+fix_itertools_imports.pyi fix_xrange.pyi
+fix_long.pyi fix_xreadlines.pyi
+fix_map.pyi fix_zip.pyi
+fix_metaclass.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/lib2to3/pgen2:
+__init__.pyi grammar.pyi parse.pyi token.pyi
+driver.pyi literals.pyi pgen.pyi tokenize.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/logging:
+__init__.pyi config.pyi handlers.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/msilib:
+__init__.pyi schema.pyi sequence.pyi text.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/multiprocessing:
+__init__.pyi popen_fork.pyi resource_tracker.pyi
+connection.pyi popen_forkserver.pyi shared_memory.pyi
+context.pyi popen_spawn_posix.pyi sharedctypes.pyi
+dummy popen_spawn_win32.pyi spawn.pyi
+forkserver.pyi process.pyi synchronize.pyi
+heap.pyi queues.pyi util.pyi
+managers.pyi reduction.pyi
+pool.pyi resource_sharer.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/multiprocessing/dummy:
+__init__.pyi connection.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/os:
+__init__.pyi path.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/pathlib:
+__init__.pyi types.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/pydoc_data:
+__init__.pyi topics.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/pyexpat:
+__init__.pyi errors.pyi model.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/sqlite3:
+__init__.pyi dbapi2.pyi dump.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/string:
+__init__.pyi templatelib.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/sys:
+__init__.pyi _monitoring.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/tkinter:
+__init__.pyi dnd.pyi simpledialog.pyi
+colorchooser.pyi filedialog.pyi tix.pyi
+commondialog.pyi font.pyi ttk.pyi
+constants.pyi messagebox.pyi
+dialog.pyi scrolledtext.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/unittest:
+__init__.pyi case.pyi mock.pyi signals.pyi
+_log.pyi loader.pyi result.pyi suite.pyi
+async_case.pyi main.pyi runner.pyi util.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/urllib:
+__init__.pyi parse.pyi response.pyi
+error.pyi request.pyi robotparser.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/venv:
+__init__.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/wsgiref:
+__init__.pyi simple_server.pyi validate.pyi
+handlers.pyi types.pyi
+headers.pyi util.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/xml:
+__init__.pyi dom etree parsers sax
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/xml/dom:
+NodeFilter.pyi expatbuilder.pyi pulldom.pyi
+__init__.pyi minicompat.pyi xmlbuilder.pyi
+domreg.pyi minidom.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/xml/etree:
+ElementInclude.pyi ElementTree.pyi cElementTree.pyi
+ElementPath.pyi __init__.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/xml/parsers:
+__init__.pyi expat
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/xml/parsers/expat:
+__init__.pyi errors.pyi model.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/xml/sax:
+__init__.pyi expatreader.pyi saxutils.pyi
+_exceptions.pyi handler.pyi xmlreader.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/xmlrpc:
+__init__.pyi client.pyi server.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/zipfile:
+__init__.pyi _path
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/zipfile/_path:
+__init__.pyi glob.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stdlib/zoneinfo:
+__init__.pyi _common.pyi _tzpath.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stubs:
+librt mypy-extensions
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stubs/librt:
+librt
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stubs/librt/librt:
+__init__.pyi base64.pyi internal.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/typeshed/stubs/mypy-extensions:
+mypy_extensions.pyi
+./aylm_env/lib/python3.11/site-packages/mypy/xml:
+mypy-html.css mypy-html.xslt mypy-txt.xslt mypy.xsd
+./aylm_env/lib/python3.11/site-packages/mypy-1.19.1.dist-info:
+INSTALLER REQUESTED licenses
+METADATA WHEEL top_level.txt
+RECORD entry_points.txt
+./aylm_env/lib/python3.11/site-packages/mypy-1.19.1.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/mypy_extensions-1.1.0.dist-info:
+INSTALLER METADATA RECORD WHEEL licenses
+./aylm_env/lib/python3.11/site-packages/mypy_extensions-1.1.0.dist-info/licenses:
+LICENSE
+./aylm_env/lib/python3.11/site-packages/mypyc:
+__init__.cpython-311-darwin.so irbuild
+__init__.py lib-rt
+__main__.py lower
+__pycache__ namegen.cpython-311-darwin.so
+analysis namegen.py
+annotate.cpython-311-darwin.so options.cpython-311-darwin.so
+annotate.py options.py
+build.cpython-311-darwin.so primitives
+build.py py.typed
+build_setup.py rt_subtype.cpython-311-darwin.so
+codegen rt_subtype.py
+common.cpython-311-darwin.so sametype.cpython-311-darwin.so
+common.py sametype.py
+crash.cpython-311-darwin.so subtype.cpython-311-darwin.so
+crash.py subtype.py
+errors.cpython-311-darwin.so test
+errors.py transform
+ir
+./aylm_env/lib/python3.11/site-packages/mypyc/__pycache__:
+__init__.cpython-311.pyc errors.cpython-311.pyc
+__main__.cpython-311.pyc namegen.cpython-311.pyc
+annotate.cpython-311.pyc options.cpython-311.pyc
+build.cpython-311.pyc rt_subtype.cpython-311.pyc
+build_setup.cpython-311.pyc sametype.cpython-311.pyc
+common.cpython-311.pyc subtype.cpython-311.pyc
+crash.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypyc/analysis:
+__init__.cpython-311-darwin.so capsule_deps.py
+__init__.py dataflow.cpython-311-darwin.so
+__pycache__ dataflow.py
+attrdefined.cpython-311-darwin.so ircheck.cpython-311-darwin.so
+attrdefined.py ircheck.py
+blockfreq.cpython-311-darwin.so selfleaks.cpython-311-darwin.so
+blockfreq.py selfleaks.py
+capsule_deps.cpython-311-darwin.so
+./aylm_env/lib/python3.11/site-packages/mypyc/analysis/__pycache__:
+__init__.cpython-311.pyc dataflow.cpython-311.pyc
+attrdefined.cpython-311.pyc ircheck.cpython-311.pyc
+blockfreq.cpython-311.pyc selfleaks.cpython-311.pyc
+capsule_deps.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypyc/codegen:
+__init__.cpython-311-darwin.so emitfunc.cpython-311-darwin.so
+__init__.py emitfunc.py
+__pycache__ emitmodule.cpython-311-darwin.so
+cstring.cpython-311-darwin.so emitmodule.py
+cstring.py emitwrapper.cpython-311-darwin.so
+emit.cpython-311-darwin.so emitwrapper.py
+emit.py literals.cpython-311-darwin.so
+emitclass.cpython-311-darwin.so literals.py
+emitclass.py
+./aylm_env/lib/python3.11/site-packages/mypyc/codegen/__pycache__:
+__init__.cpython-311.pyc emitfunc.cpython-311.pyc
+cstring.cpython-311.pyc emitmodule.cpython-311.pyc
+emit.cpython-311.pyc emitwrapper.cpython-311.pyc
+emitclass.cpython-311.pyc literals.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypyc/ir:
+__init__.cpython-311-darwin.so module_ir.py
+__init__.py ops.cpython-311-darwin.so
+__pycache__ ops.py
+class_ir.cpython-311-darwin.so pprint.cpython-311-darwin.so
+class_ir.py pprint.py
+func_ir.cpython-311-darwin.so rtypes.cpython-311-darwin.so
+func_ir.py rtypes.py
+module_ir.cpython-311-darwin.so
+./aylm_env/lib/python3.11/site-packages/mypyc/ir/__pycache__:
+__init__.cpython-311.pyc ops.cpython-311.pyc
+class_ir.cpython-311.pyc pprint.cpython-311.pyc
+func_ir.cpython-311.pyc rtypes.cpython-311.pyc
+module_ir.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypyc/irbuild:
+__init__.cpython-311-darwin.so
+__init__.py
+__pycache__
+ast_helpers.cpython-311-darwin.so
+ast_helpers.py
+builder.cpython-311-darwin.so
+builder.py
+callable_class.cpython-311-darwin.so
+callable_class.py
+classdef.cpython-311-darwin.so
+classdef.py
+constant_fold.cpython-311-darwin.so
+constant_fold.py
+context.cpython-311-darwin.so
+context.py
+env_class.cpython-311-darwin.so
+env_class.py
+expression.cpython-311-darwin.so
+expression.py
+for_helpers.cpython-311-darwin.so
+for_helpers.py
+format_str_tokenizer.cpython-311-darwin.so
+format_str_tokenizer.py
+function.cpython-311-darwin.so
+function.py
+generator.cpython-311-darwin.so
+generator.py
+ll_builder.cpython-311-darwin.so
+ll_builder.py
+main.cpython-311-darwin.so
+main.py
+mapper.cpython-311-darwin.so
+mapper.py
+match.cpython-311-darwin.so
+match.py
+missingtypevisitor.cpython-311-darwin.so
+missingtypevisitor.py
+nonlocalcontrol.cpython-311-darwin.so
+nonlocalcontrol.py
+prebuildvisitor.cpython-311-darwin.so
+prebuildvisitor.py
+prepare.cpython-311-darwin.so
+prepare.py
+specialize.cpython-311-darwin.so
+specialize.py
+statement.cpython-311-darwin.so
+statement.py
+targets.cpython-311-darwin.so
+targets.py
+util.cpython-311-darwin.so
+util.py
+visitor.cpython-311-darwin.so
+visitor.py
+vtable.cpython-311-darwin.so
+vtable.py
+./aylm_env/lib/python3.11/site-packages/mypyc/irbuild/__pycache__:
+__init__.cpython-311.pyc main.cpython-311.pyc
+ast_helpers.cpython-311.pyc mapper.cpython-311.pyc
+builder.cpython-311.pyc match.cpython-311.pyc
+callable_class.cpython-311.pyc missingtypevisitor.cpython-311.pyc
+classdef.cpython-311.pyc nonlocalcontrol.cpython-311.pyc
+constant_fold.cpython-311.pyc prebuildvisitor.cpython-311.pyc
+context.cpython-311.pyc prepare.cpython-311.pyc
+env_class.cpython-311.pyc specialize.cpython-311.pyc
+expression.cpython-311.pyc statement.cpython-311.pyc
+for_helpers.cpython-311.pyc targets.cpython-311.pyc
+format_str_tokenizer.cpython-311.pyc util.cpython-311.pyc
+function.cpython-311.pyc visitor.cpython-311.pyc
+generator.cpython-311.pyc vtable.cpython-311.pyc
+ll_builder.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt:
+CPy.h librt_internal.c
+base64 librt_internal.h
+bytes_ops.c list_ops.c
+dict_ops.c misc_ops.c
+exc_ops.c module_shim.tmpl
+float_ops.c module_shim_no_gil_multiphase.tmpl
+generic_ops.c mypyc_util.h
+getargs.c pythoncapi_compat.h
+getargsfast.c pythonsupport.c
+init.c pythonsupport.h
+int_ops.c set_ops.c
+librt_base64.c str_ops.c
+librt_base64.h tuple_ops.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64:
+arch codecs.h env.h libbase64.h
+codec_choose.c config.h lib.c tables
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch:
+avx avx2 avx512 generic neon32 neon64 sse41 sse42 ssse3
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch/avx:
+codec.c enc_loop_asm.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch/avx2:
+codec.c dec_reshuffle.c enc_loop_asm.c enc_translate.c
+dec_loop.c enc_loop.c enc_reshuffle.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch/avx512:
+codec.c enc_reshuffle_translate.c
+enc_loop.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch/generic:
+32 codec.c dec_tail.c enc_tail.c
+64 dec_head.c enc_head.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch/generic/32:
+dec_loop.c enc_loop.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch/generic/64:
+enc_loop.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch/neon32:
+codec.c dec_loop.c enc_loop.c enc_reshuffle.c enc_translate.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch/neon64:
+codec.c dec_loop.c enc_loop.c enc_loop_asm.c enc_reshuffle.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch/sse41:
+codec.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch/sse42:
+codec.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/arch/ssse3:
+codec.c dec_reshuffle.c enc_loop_asm.c enc_translate.c
+dec_loop.c enc_loop.c enc_reshuffle.c
+./aylm_env/lib/python3.11/site-packages/mypyc/lib-rt/base64/tables:
+table_dec_32bit.h tables.c
+table_enc_12bit.h tables.h
+./aylm_env/lib/python3.11/site-packages/mypyc/lower:
+__init__.cpython-311-darwin.so list_ops.py
+__init__.py misc_ops.cpython-311-darwin.so
+__pycache__ misc_ops.py
+int_ops.cpython-311-darwin.so registry.cpython-311-darwin.so
+int_ops.py registry.py
+list_ops.cpython-311-darwin.so
+./aylm_env/lib/python3.11/site-packages/mypyc/lower/__pycache__:
+__init__.cpython-311.pyc misc_ops.cpython-311.pyc
+int_ops.cpython-311.pyc registry.cpython-311.pyc
+list_ops.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypyc/primitives:
+__init__.cpython-311-darwin.so list_ops.cpython-311-darwin.so
+__init__.py list_ops.py
+__pycache__ misc_ops.cpython-311-darwin.so
+bytes_ops.cpython-311-darwin.so misc_ops.py
+bytes_ops.py registry.cpython-311-darwin.so
+dict_ops.cpython-311-darwin.so registry.py
+dict_ops.py set_ops.cpython-311-darwin.so
+exc_ops.cpython-311-darwin.so set_ops.py
+exc_ops.py str_ops.cpython-311-darwin.so
+float_ops.cpython-311-darwin.so str_ops.py
+float_ops.py tuple_ops.cpython-311-darwin.so
+generic_ops.cpython-311-darwin.so tuple_ops.py
+generic_ops.py weakref_ops.cpython-311-darwin.so
+int_ops.cpython-311-darwin.so weakref_ops.py
+int_ops.py
+./aylm_env/lib/python3.11/site-packages/mypyc/primitives/__pycache__:
+__init__.cpython-311.pyc list_ops.cpython-311.pyc
+bytes_ops.cpython-311.pyc misc_ops.cpython-311.pyc
+dict_ops.cpython-311.pyc registry.cpython-311.pyc
+exc_ops.cpython-311.pyc set_ops.cpython-311.pyc
+float_ops.cpython-311.pyc str_ops.cpython-311.pyc
+generic_ops.cpython-311.pyc tuple_ops.cpython-311.pyc
+int_ops.cpython-311.pyc weakref_ops.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypyc/test:
+__init__.py test_emitfunc.py test_optimizations.py
+__pycache__ test_emitwrapper.py test_pprint.py
+config.py test_exceptions.py test_rarray.py
+test_alwaysdefined.py test_external.py test_refcount.py
+test_analysis.py test_irbuild.py test_run.py
+test_annotate.py test_ircheck.py test_serialization.py
+test_cheader.py test_literals.py test_struct.py
+test_commandline.py test_lowering.py test_tuplename.py
+test_emit.py test_misc.py test_typeops.py
+test_emitclass.py test_namegen.py testutil.py
+./aylm_env/lib/python3.11/site-packages/mypyc/test/__pycache__:
+__init__.cpython-311.pyc test_literals.cpython-311.pyc
+config.cpython-311.pyc test_lowering.cpython-311.pyc
+test_alwaysdefined.cpython-311.pyc test_misc.cpython-311.pyc
+test_analysis.cpython-311.pyc test_namegen.cpython-311.pyc
+test_annotate.cpython-311.pyc test_optimizations.cpython-311.pyc
+test_cheader.cpython-311.pyc test_pprint.cpython-311.pyc
+test_commandline.cpython-311.pyc test_rarray.cpython-311.pyc
+test_emit.cpython-311.pyc test_refcount.cpython-311.pyc
+test_emitclass.cpython-311.pyc test_run.cpython-311.pyc
+test_emitfunc.cpython-311.pyc test_serialization.cpython-311.pyc
+test_emitwrapper.cpython-311.pyc test_struct.cpython-311.pyc
+test_exceptions.cpython-311.pyc test_tuplename.cpython-311.pyc
+test_external.cpython-311.pyc test_typeops.cpython-311.pyc
+test_irbuild.cpython-311.pyc testutil.cpython-311.pyc
+test_ircheck.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/mypyc/transform:
+__init__.cpython-311-darwin.so log_trace.cpython-311-darwin.so
+__init__.py log_trace.py
+__pycache__ lower.cpython-311-darwin.so
+copy_propagation.cpython-311-darwin.so lower.py
+copy_propagation.py refcount.cpython-311-darwin.so
+exceptions.cpython-311-darwin.so refcount.py
+exceptions.py spill.cpython-311-darwin.so
+flag_elimination.cpython-311-darwin.so spill.py
+flag_elimination.py uninit.cpython-311-darwin.so
+ir_transform.cpython-311-darwin.so uninit.py
+ir_transform.py
+./aylm_env/lib/python3.11/site-packages/mypyc/transform/__pycache__:
+__init__.cpython-311.pyc log_trace.cpython-311.pyc
+copy_propagation.cpython-311.pyc lower.cpython-311.pyc
+exceptions.cpython-311.pyc refcount.cpython-311.pyc
+flag_elimination.cpython-311.pyc spill.cpython-311.pyc
+ir_transform.cpython-311.pyc uninit.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals:
+__init__.py _sql group_by.py
+__pycache__ _translate.py plugins.py
+_arrow _typing.py py.typed
+_compliant _typing_compat.py schema.py
+_constants.py _utils.py selectors.py
+_dask compliant.py series.py
+_duckdb dataframe.py series_cat.py
+_duration.py dependencies.py series_dt.py
+_enum.py dtypes.py series_list.py
+_exceptions.py exceptions.py series_str.py
+_expression_parsing.py expr.py series_struct.py
+_ibis expr_cat.py sql.py
+_interchange expr_dt.py stable
+_namespace.py expr_list.py testing
+_native.py expr_name.py this.py
+_pandas_like expr_str.py translate.py
+_polars expr_struct.py typing.py
+_spark_like functions.py utils.py
+./aylm_env/lib/python3.11/site-packages/narwhals/__pycache__:
+__init__.cpython-311.pyc expr_list.cpython-311.pyc
+_constants.cpython-311.pyc expr_name.cpython-311.pyc
+_duration.cpython-311.pyc expr_str.cpython-311.pyc
+_enum.cpython-311.pyc expr_struct.cpython-311.pyc
+_exceptions.cpython-311.pyc functions.cpython-311.pyc
+_expression_parsing.cpython-311.pyc group_by.cpython-311.pyc
+_namespace.cpython-311.pyc plugins.cpython-311.pyc
+_native.cpython-311.pyc schema.cpython-311.pyc
+_translate.cpython-311.pyc selectors.cpython-311.pyc
+_typing.cpython-311.pyc series.cpython-311.pyc
+_typing_compat.cpython-311.pyc series_cat.cpython-311.pyc
+_utils.cpython-311.pyc series_dt.cpython-311.pyc
+compliant.cpython-311.pyc series_list.cpython-311.pyc
+dataframe.cpython-311.pyc series_str.cpython-311.pyc
+dependencies.cpython-311.pyc series_struct.cpython-311.pyc
+dtypes.cpython-311.pyc sql.cpython-311.pyc
+exceptions.cpython-311.pyc this.cpython-311.pyc
+expr.cpython-311.pyc translate.cpython-311.pyc
+expr_cat.cpython-311.pyc typing.cpython-311.pyc
+expr_dt.cpython-311.pyc utils.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/_arrow:
+__init__.py namespace.py series_list.py
+__pycache__ selectors.py series_str.py
+dataframe.py series.py series_struct.py
+expr.py series_cat.py typing.py
+group_by.py series_dt.py utils.py
+./aylm_env/lib/python3.11/site-packages/narwhals/_arrow/__pycache__:
+__init__.cpython-311.pyc series_cat.cpython-311.pyc
+dataframe.cpython-311.pyc series_dt.cpython-311.pyc
+expr.cpython-311.pyc series_list.cpython-311.pyc
+group_by.cpython-311.pyc series_str.cpython-311.pyc
+namespace.cpython-311.pyc series_struct.cpython-311.pyc
+selectors.cpython-311.pyc typing.cpython-311.pyc
+series.cpython-311.pyc utils.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/_compliant:
+__init__.py dataframe.py selectors.py
+__pycache__ expr.py series.py
+any_namespace.py group_by.py typing.py
+column.py namespace.py window.py
+./aylm_env/lib/python3.11/site-packages/narwhals/_compliant/__pycache__:
+__init__.cpython-311.pyc namespace.cpython-311.pyc
+any_namespace.cpython-311.pyc selectors.cpython-311.pyc
+column.cpython-311.pyc series.cpython-311.pyc
+dataframe.cpython-311.pyc typing.cpython-311.pyc
+expr.cpython-311.pyc window.cpython-311.pyc
+group_by.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/_dask:
+__init__.py dataframe.py expr_dt.py group_by.py selectors.py
+__pycache__ expr.py expr_str.py namespace.py utils.py
+./aylm_env/lib/python3.11/site-packages/narwhals/_dask/__pycache__:
+__init__.cpython-311.pyc group_by.cpython-311.pyc
+dataframe.cpython-311.pyc namespace.cpython-311.pyc
+expr.cpython-311.pyc selectors.cpython-311.pyc
+expr_dt.cpython-311.pyc utils.cpython-311.pyc
+expr_str.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/_duckdb:
+__init__.py expr.py expr_str.py namespace.py typing.py
+__pycache__ expr_dt.py expr_struct.py selectors.py utils.py
+dataframe.py expr_list.py group_by.py series.py
+./aylm_env/lib/python3.11/site-packages/narwhals/_duckdb/__pycache__:
+__init__.cpython-311.pyc group_by.cpython-311.pyc
+dataframe.cpython-311.pyc namespace.cpython-311.pyc
+expr.cpython-311.pyc selectors.cpython-311.pyc
+expr_dt.cpython-311.pyc series.cpython-311.pyc
+expr_list.cpython-311.pyc typing.cpython-311.pyc
+expr_str.cpython-311.pyc utils.cpython-311.pyc
+expr_struct.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/_ibis:
+__init__.py expr.py expr_str.py namespace.py utils.py
+__pycache__ expr_dt.py expr_struct.py selectors.py
+dataframe.py expr_list.py group_by.py series.py
+./aylm_env/lib/python3.11/site-packages/narwhals/_ibis/__pycache__:
+__init__.cpython-311.pyc expr_struct.cpython-311.pyc
+dataframe.cpython-311.pyc group_by.cpython-311.pyc
+expr.cpython-311.pyc namespace.cpython-311.pyc
+expr_dt.cpython-311.pyc selectors.cpython-311.pyc
+expr_list.cpython-311.pyc series.cpython-311.pyc
+expr_str.cpython-311.pyc utils.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/_interchange:
+__init__.py __pycache__ dataframe.py series.py
+./aylm_env/lib/python3.11/site-packages/narwhals/_interchange/__pycache__:
+__init__.cpython-311.pyc series.cpython-311.pyc
+dataframe.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/_pandas_like:
+__init__.py namespace.py series_list.py
+__pycache__ selectors.py series_str.py
+dataframe.py series.py series_struct.py
+expr.py series_cat.py typing.py
+group_by.py series_dt.py utils.py
+./aylm_env/lib/python3.11/site-packages/narwhals/_pandas_like/__pycache__:
+__init__.cpython-311.pyc series_cat.cpython-311.pyc
+dataframe.cpython-311.pyc series_dt.cpython-311.pyc
+expr.cpython-311.pyc series_list.cpython-311.pyc
+group_by.cpython-311.pyc series_str.cpython-311.pyc
+namespace.cpython-311.pyc series_struct.cpython-311.pyc
+selectors.cpython-311.pyc typing.cpython-311.pyc
+series.cpython-311.pyc utils.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/_polars:
+__init__.py dataframe.py group_by.py series.py utils.py
+__pycache__ expr.py namespace.py typing.py
+./aylm_env/lib/python3.11/site-packages/narwhals/_polars/__pycache__:
+__init__.cpython-311.pyc namespace.cpython-311.pyc
+dataframe.cpython-311.pyc series.cpython-311.pyc
+expr.cpython-311.pyc typing.cpython-311.pyc
+group_by.cpython-311.pyc utils.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/_spark_like:
+__init__.py expr.py expr_str.py namespace.py
+__pycache__ expr_dt.py expr_struct.py selectors.py
+dataframe.py expr_list.py group_by.py utils.py
+./aylm_env/lib/python3.11/site-packages/narwhals/_spark_like/__pycache__:
+__init__.cpython-311.pyc expr_struct.cpython-311.pyc
+dataframe.cpython-311.pyc group_by.cpython-311.pyc
+expr.cpython-311.pyc namespace.cpython-311.pyc
+expr_dt.cpython-311.pyc selectors.cpython-311.pyc
+expr_list.cpython-311.pyc utils.cpython-311.pyc
+expr_str.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/_sql:
+__init__.py dataframe.py expr_dt.py group_by.py typing.py
+__pycache__ expr.py expr_str.py namespace.py
+./aylm_env/lib/python3.11/site-packages/narwhals/_sql/__pycache__:
+__init__.cpython-311.pyc expr_str.cpython-311.pyc
+dataframe.cpython-311.pyc group_by.cpython-311.pyc
+expr.cpython-311.pyc namespace.cpython-311.pyc
+expr_dt.cpython-311.pyc typing.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/stable:
+__init__.py __pycache__ v1 v2
+./aylm_env/lib/python3.11/site-packages/narwhals/stable/__pycache__:
+__init__.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/stable/v1:
+__init__.py _dtypes.py dependencies.py selectors.py
+__pycache__ _namespace.py dtypes.py typing.py
+./aylm_env/lib/python3.11/site-packages/narwhals/stable/v1/__pycache__:
+__init__.cpython-311.pyc dtypes.cpython-311.pyc
+_dtypes.cpython-311.pyc selectors.cpython-311.pyc
+_namespace.cpython-311.pyc typing.cpython-311.pyc
+dependencies.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/stable/v2:
+__init__.py _namespace.py dtypes.py typing.py
+__pycache__ dependencies.py selectors.py
+./aylm_env/lib/python3.11/site-packages/narwhals/stable/v2/__pycache__:
+__init__.cpython-311.pyc dtypes.cpython-311.pyc
+_namespace.cpython-311.pyc selectors.cpython-311.pyc
+dependencies.cpython-311.pyc typing.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/testing:
+__init__.py __pycache__ asserts
+./aylm_env/lib/python3.11/site-packages/narwhals/testing/__pycache__:
+__init__.cpython-311.pyc
+./aylm_env/lib/python3.11/site-packages/narwhals/testing/asserts:
+__init__.py __pycache__ frame.py series.py utils.py
+./aylm_env/lib/python3.11/site-packages/narwhals/testing/asserts/__pycache__:
+LINE LIMIT EXCEEDED
\ No newline at end of file
diff --git a/README.md b/README.md
index 97ee752..cfcf8ea 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,421 @@
-# A.YLM
+# A-YLM: Geometric Constitutional AI for Embodied Intelligence
-Single-image 3D reconstruction and intelligent navigation system based on Apple SHARP model.
+
[](https://opensource.org/licenses/MIT)
[](https://www.python.org/downloads/)
+[](https://arxiv.org)
-## Features
+**Self-Supervised Safety Framework | Autonomous Driving | Robotics | Embodied AI**
-- **Single-Image 3D Reconstruction**: Convert a single RGB image into a 3D Gaussian Splatting model using Apple's SHARP Vision Transformer
-- **Semantic Fusion**: YOLO-based object detection with 3D point cloud semantic labeling
-- **Obstacle Detection**: Automatic identification of vehicles, pedestrians, bicycles, and other obstacles
-- **Navigation Mesh Generation**: Generate robot-navigable 3D voxel meshes with obstacle information
-- **Pipeline Processing**: Parallel processing for multiple images (inference + voxelization)
-- **Video Processing**: Extract frames from video and process as image sequence
-- **Ground Detection**: RANSAC-based ground plane detection with normal vector validation
-- **Coordinate Transformation**: OpenCV to Robot/ENU coordinate system conversion
-- **Multi-Format Support**: 68+ image formats including HEIC, JPEG, PNG, WEBP, AVIF, TIFF, PSD
+*Extending Anthropic's Constitutional AI paradigm from language to the physical world*
-## System Requirements
+[Paper](#) | [Documentation](#) | [Demo](#) | [中文文档](docs/paper_aylm_zh.md)
-- Python 3.9+ (Python 3.11 recommended for Open3D compatibility)
-- PyTorch 2.0+
-- 4GB+ RAM
-- GPU recommended (supports CUDA, MPS for Apple Silicon)
+
-### Optional Dependencies
+---
+
+## Vision: Physical Constitution for AI
+
+> *"Just as Constitutional AI teaches language models ethical boundaries through textual principles, A-YLM teaches embodied AI systems physical boundaries through geometric constraints."*
+
+A-YLM represents a paradigm shift in AI safety — from **reactive safety checks** to **constitutional safety learning**. We believe that for AI to truly understand and safely navigate the physical world, it needs more than perception; it needs a **geometric constitution** that defines the fundamental laws of physical interaction.
+
+---
+
+## Abstract
+
+A-YLM is a **Geometric Constitutional AI** framework for embodied intelligence systems. Inspired by Anthropic's Constitutional AI approach for language models, A-YLM extends this safety paradigm to the **physical world** — providing geometric constraints as a "Physical Constitution" that governs AI behavior in 3D space.
+
+The core philosophy: **AI needs physical laws, not just language rules**. While Constitutional AI teaches language models ethical boundaries through textual principles, A-YLM teaches embodied AI systems physical boundaries through geometric constraints. This enables AI to **understand the world** through self-supervised learning from massive geometric data.
+
+### Key Innovations
+
+| Concept | Language AI (Constitutional AI) | Embodied AI (A-YLM) |
+|---------|--------------------------------|---------------------|
+| **Constitution** | Ethical principles in text | Geometric constraints in 3D |
+| **Supervision** | RLHF with human feedback | Self-supervised geometric validation |
+| **Learning** | Learn from text corrections | Learn from physical violations |
+| **Safety** | Prevent harmful outputs | Prevent unsafe physical actions |
+| **Evolution** | Iterative refinement | Continuous geometric feedback |
+
+### Core Capabilities
+
+- **Geometric Constitutional Supervision**: Physical laws as AI behavior boundaries
+- **Self-Supervised World Understanding**: AI learns physics through geometric feedback — no human labeling required
+- **Embodied Intelligence Training**: Continuous self-evolution from massive 3D data
+- **Universal Applicability**: Not just autonomous driving — all embodied AI systems (robotics, drones, AR/VR, humanoids)
+- **Edge-Deployable**: Real-time geometric supervision on resource-constrained devices
+
+The system pipeline: **Sensor Input → 3DGS Reconstruction → Voxelization → Constitutional Validation → Geometric Feedback → Self-Learning**.
+
+---
+
+## Why Geometric Constitutional AI?
+
+### The Problem with Current AI Safety
+
+Current embodied AI systems face a fundamental challenge:
+
+| Approach | Limitation |
+|----------|------------|
+| **Rule-based Safety** | Cannot generalize to novel situations |
+| **Learned Safety** | Requires massive human-labeled data |
+| **End-to-End Models** | Black-box decisions, uninterpretable |
+| **Simulation-based** | Sim-to-real gap, limited coverage |
+
+### Our Solution: Bidirectional Fusion with E2E Systems
+
+A-YLM introduces **Geometric Constitutional AI** with **bidirectional fusion** — fully compatible with existing end-to-end driving systems (Tesla FSD, Huawei ADS, etc.):
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Bidirectional Fusion Architecture │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ End-to-End Driving AI (FSD/ADS) │ │
+│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
+│ │ │ Video │───▶│ Neural │───▶│ Decision │───▶│ Control │ │ │
+│ │ │ Input │ │ Network │ │ Output │ │ Signal │ │ │
+│ │ └──────────┘ └────┬─────┘ └────┬─────┘ └──────────┘ │ │
+│ │ │ │ │ │
+│ │ │ 3D Input │ Decision │ │
+│ │ ▼ ▼ │ │
+│ └────────────────────────┼───────────────┼────────────────────────────┘ │
+│ │ │ │
+│ ┌────────────────────────┼───────────────┼────────────────────────────┐ │
+│ │ A-YLM Edge Module (Lightweight) │ │
+│ │ │ │ │ │
+│ │ ┌──────────┐ ┌────▼─────┐ ┌────▼─────┐ ┌──────────┐ │ │
+│ │ │ Camera │───▶│ 3D GS │───▶│ Safety │───▶│ Training │ │ │
+│ │ │ Input │ │ Recon │ │ Scoring │ │ Signal │ │ │
+│ │ └──────────┘ └────┬─────┘ └──────────┘ └──────────┘ │ │
+│ │ │ │ │
+│ │ │ Point Cloud + Color │ │
+│ │ ▼ │ │
+│ │ ┌──────────────┐ │ │
+│ │ │ 3D Scene │ ──▶ Available for E2E AI Input │ │
+│ │ │ with Color │ │ │
+│ │ └──────────────┘ │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+**Direction 1: Safety Supervision (A-YLM → E2E AI)**
+- A-YLM monitors and validates E2E AI decisions in real-time
+- Provides safety scores and violation labels
+- Generates training signals for AI self-improvement (no human labeling)
+
+**Direction 2: 3D Input Enhancement (A-YLM → E2E AI)**
+- A-YLM provides 3D point cloud as additional input to E2E AI
+- Includes color information from 3D Gaussian Splatting
+- Enables E2E AI to perceive 3D geometry directly
+- Fully compatible with existing video-based E2E architectures
+
+**Key Advantages:**
+1. **Full Compatibility**: Works with existing E2E systems (FSD, ADS) without modification
+2. **Enhanced Perception**: E2E AI gains 3D geometric understanding
+3. **Local Safety Guarantee**: Edge module provides real-time safety validation
+4. **Lightweight & Edge-Optimized**: Runs on Jetson, Apple MPS, and other edge devices
+5. **Self-Supervised Learning**: AI evolves through geometric feedback, no human annotation needed
+
+### Local Safety Decision Module
+
+A-YLM provides a **standalone local safety decision module** that can be called as an independent component:
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ A-YLM Local Safety Decision Module │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Input: Camera Frame │
+│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
+│ │ Camera │───▶│ 3D GS │───▶│ Point │───▶│ Depth │ │
+│ │ Frame │ │ Recon │ │ Cloud │ │ Info │ │
+│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
+│ │ │
+│ ▼ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ Safety Scoring Module (Our Contribution) │ │
+│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
+│ │ │ Collision │ │ TTC │ │ Boundary │ │ │
+│ │ │ Detection │ │ Calculation │ │ Validation │ │ │
+│ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │
+│ │ │ │ │ │ │
+│ │ └─────────────────┼─────────────────┘ │ │
+│ │ ▼ │ │
+│ │ ┌──────────────┐ │ │
+│ │ │ Safety Score │ ──▶ 0.0 (Dangerous) ~ 1.0 (Safe) │ │
+│ │ └──────────────┘ │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │ │
+│ Output: ▼ │
+│ ┌──────────────────────────────────────────────────────────────────────┐ │
+│ │ - Safety Score (0.0 ~ 1.0) │ │
+│ │ - Violation Labels (collision, ttc_warning, boundary) │ │
+│ │ - 3D Obstacle Positions with Depth │ │
+│ │ - Recommended Action (safe/warning/emergency_stop) │ │
+│ │ - Training Signal for Cloud AI │ │
+│ └──────────────────────────────────────────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+**Module Features:**
+- **3D Decision Making**: With point cloud and depth information, enables 3D-based safety decisions locally
+- **Safety Scoring**: Our proposed scoring module evaluates AI decisions against geometric constraints
+- **Modular Design**: Can be called as an independent module by any system
+- **Real-time**: Optimized for edge deployment with <50ms latency
+- **API-Ready**: Simple Python API for integration
+
+```python
+# Example: Using A-YLM as a safety module
+from aylm.safety import SafetyModule
+
+safety = SafetyModule()
+
+# Get safety score for an AI decision
+result = safety.evaluate(
+ camera_frame=frame,
+ ai_decision={"steering": 0.1, "acceleration": 0.5}
+)
+
+print(f"Safety Score: {result.score}") # 0.0 ~ 1.0
+print(f"Violations: {result.violations}") # ['ttc_warning']
+print(f"Recommended: {result.action}") # 'reduce_speed'
+print(f"Training Signal: {result.training_label}") # For cloud AI
+```
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Geometric Constitutional AI Loop │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ AI │ ──────▶ │ Geometric │ ──────▶ │ Physical │ │
+│ │ Decision │ │ Validation │ │ Execution │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+│ ▲ │ │ │
+│ │ │ Violation? │ │
+│ │ ▼ │ │
+│ ┌─────────────┐ ┌─────────────┐ │ │
+│ │ World │ ◀────── │ Feedback │ ◀─────────────┘ │
+│ │ Model │ │ Generation │ Physical Outcome │
+│ │ Update │ └─────────────┘ │
+│ └─────────────┘ │
+│ │ │
+│ └──────────────── Self-Evolution ────────────────────────────────│
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 1. Geometric Constitutional AI: A New Safety Paradigm
+
+### 1.1 From Language Constitution to Physical Constitution
+
+Anthropic's Constitutional AI revolutionized language model safety by embedding ethical principles directly into the training process. A-YLM extends this paradigm to **embodied intelligence**:
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Constitutional AI Paradigm Comparison │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Language AI (Anthropic) Embodied AI (A-YLM) │
+│ ┌─────────────────────┐ ┌─────────────────────┐ │
+│ │ Text Constitution │ │ Geometric Constitution│ │
+│ │ "Be helpful, │ │ "Respect physical │ │
+│ │ harmless, honest" │ │ boundaries, avoid │ │
+│ └─────────────────────┘ │ collisions, obey │ │
+│ │ │ spatial constraints"│ │
+│ ▼ └─────────────────────┘ │
+│ ┌─────────────────────┐ │ │
+│ │ RLHF Training │ ▼ │
+│ │ (Human Feedback) │ ┌─────────────────────┐ │
+│ └─────────────────────┘ │ Self-Supervised │ │
+│ │ │ Geometric Learning │ │
+│ ▼ └─────────────────────┘ │
+│ ┌─────────────────────┐ │ │
+│ │ Safe Language │ ▼ │
+│ │ Outputs │ ┌─────────────────────┐ │
+│ └─────────────────────┘ │ Safe Physical │ │
+│ │ Actions │ │
+│ └─────────────────────┘ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 1.2 Self-Supervised World Understanding (Embodied Intelligence)
+
+A-YLM enables AI to **understand the physical world** through continuous geometric learning — the essence of **embodied intelligence**:
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Self-Supervised World Understanding │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Stage 1: Geometric Data Collection │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ Camera → 3DGS Reconstruction → Voxel Grid → Physical Ground Truth │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ Stage 2: Constitutional Validation │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ AI Decision → Geometric Check → Collision? Boundary? TTC? │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ Stage 3: Violation Detection & Feedback │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ Violation Detected → Generate Training Signal → Update World Model │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ Stage 4: AI Self-Evolution │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ Improved World Understanding → Better Decisions → Safer Actions │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+This creates a **self-improving loop** where AI learns physics without human labeling — true embodied intelligence.
+
+### 1.3 Universal Embodied Intelligence Applications
+
+While autonomous driving is our primary demonstration, A-YLM's geometric constitutional approach applies to **all embodied AI**:
+
+| Domain | Application | Geometric Constitution | Self-Learning Signal |
+|--------|-------------|------------------------|----------------------|
+| **Autonomous Driving** | Vehicle safety | Collision avoidance, lane boundaries | Near-miss detection |
+| **Robotics** | Manipulation safety | Workspace limits, force constraints | Contact detection |
+| **Drones/UAV** | Flight safety | Obstacle avoidance, no-fly zones | Proximity alerts |
+| **AR/VR** | Spatial interaction | Physical object boundaries | Occlusion conflicts |
+| **Humanoid Robots** | Navigation safety | Human proximity, obstacle clearance | Social distance violations |
+| **Industrial Automation** | Workspace safety | Equipment boundaries, safety zones | Zone intrusion |
+
+---
+
+## 2. Technical Architecture
+
+### 2.1 Geometric Safety Supervisor
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ A-YLM Safety Validation Pipeline │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌──────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐ │
+│ │ RGB │ │ SHARP │ │ Voxel │ │ Semantic │ │
+│ │ Input │───▶│ 3DGS │───▶│ Grid │───▶│ Fusion │ │
+│ │ (Multi- │ │ Reconstruct │ │ (Occupancy │ │ (YOLO + │ │
+│ │ view) │ │ │ │ 2.0) │ │ 3D Proj) │ │
+│ └──────────┘ └──────────────┘ └─────────────┘ └──────────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────────────────────────────────────────────────────────┐ │
+│ │ Safety Validation Module │ │
+│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
+│ │ │ Obstacle │ │ Collision │ │ Safety │ │ Geometric │ │ │
+│ │ │ Detection │ │ Prediction │ │ Boundary │ │ Feedback │ │ │
+│ │ │ (DBSCAN) │ │ (TTC) │ │ Validation │ │ Output │ │ │
+│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
+│ └──────────────────────────────────────────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 2.2 Processing Stages
+
+| Stage | Module | Input | Output | Purpose |
+|-------|--------|-------|--------|---------|
+| 1 | 3D Reconstruction | RGB Image | 3D Gaussian Splatting | Physical Ground Truth |
+| 2 | Voxelization | 3DGS Point Cloud | Occupancy Grid | Geometric Representation |
+| 3 | Ground Removal | Voxel Grid | Filtered Grid | Obstacle Isolation |
+| 4 | Object Detection | RGB Image | 2D Bounding Boxes | Semantic Understanding |
+| 5 | Semantic Fusion | 2D + 3D | Labeled Point Cloud | Safety Context |
+| 6 | Safety Validation | Semantic 3D | Safety Decisions | Geometric Verification |
+| 7 | Feedback Generation | Validation Results | Learning Signals | Self-Evolution |
+
+---
+
+## 3. Core Innovations
+
+### 3.1 Physical Ground Truth via 3DGS
+
+The 3DGS representation provides **physical ground truth** for safety validation:
+
+```
+Raw Sensor Data → 3D Gaussian Splatting → Voxel Occupancy Grid → Safety Validation
+ ↑ ↑
+ Physical Geometric
+ Reality Constraint
+```
+
+Benefits:
+- **Interpretable**: Each Gaussian corresponds to physical scene elements
+- **Verifiable**: Intermediate outputs can be visualized and validated
+- **Geometric**: Explicit 3D structure enables physics-based safety reasoning
-| Feature | Dependency | Installation |
-|---------|------------|--------------|
-| Fast voxelization | Open3D | `pip install open3d` |
-| Object detection | Ultralytics YOLO | `pip install ultralytics` |
-| Video processing | OpenCV | `pip install opencv-python` |
+### 3.2 Edge-Deployed Safety Module
-## Installation
+A-YLM is designed for **edge deployment** alongside E2E models:
-### Quick Start
+| Representation | Data Size | Inference Complexity |
+|----------------|-----------|----------------------|
+| Raw 3DGS | ~500K Gaussians | O(n²) rendering |
+| Voxel Grid (5cm) | ~50K voxels | O(n) lookup |
+| Sparse Voxel | ~10K occupied | O(k) where k << n |
+
+This compression enables **real-time safety validation** on edge computing platforms.
+
+### 3.3 Self-Supervised Safety Learning
+
+The system learns safety boundaries without manual labeling:
+
+1. **Geometric Constraint Mining**: Extract safety rules from 3D structure
+2. **Violation Detection**: Identify when E2E decisions violate geometric constraints
+3. **Feedback Generation**: Produce learning signals for model improvement
+
+### 3.4 Apple SHARP Integration
+
+We leverage Apple's **SHARP** model for efficient 3D reconstruction:
+
+- **Single-image 3D reconstruction**: Eliminates multi-view requirements
+- **Edge-optimized inference**: Designed for Apple Silicon (MPS acceleration)
+- **Real-time capable**: Suitable for safety-critical applications
+
+---
+
+## 4. System Requirements
+
+### 4.1 Hardware Requirements
+
+| Component | Minimum | Recommended |
+|-----------|---------|-------------|
+| RAM | 4GB | 16GB |
+| GPU | - | CUDA 11.0+ / Apple MPS |
+| Storage | 5GB | 20GB (with models) |
+
+### 4.2 Software Dependencies
+
+| Dependency | Version | Purpose |
+|------------|---------|---------|
+| Python | 3.9+ (3.11 recommended) | Runtime |
+| PyTorch | 2.0+ | Deep Learning Framework |
+| Open3D | 0.17+ | Point Cloud Processing |
+| Ultralytics | 8.0+ | YOLO Object Detection |
+| NumPy | 1.24+ | Numerical Computing |
+
+---
+
+## 5. Installation
+
+### 5.1 Quick Start
```bash
# Clone repository with submodules
@@ -45,95 +426,92 @@ cd A.YLM
python3.11 -m venv aylm_env
source aylm_env/bin/activate
-# Install dependencies
+# Install core dependencies
pip install -e .
pip install -e ml-sharp/
-# Install with all features (Open3D + YOLO)
+# Install full feature set
pip install -e ".[full]"
```
-### YOLO Model Setup
-
-For semantic fusion and obstacle detection, download YOLO model:
+### 5.2 Model Setup
```bash
-# Auto-download on first use, or manually:
-pip install ultralytics
+# SHARP model (~2.8GB) - auto-downloads on first use
+aylm setup --download
+
+# YOLO model (~6MB) - auto-downloads on first use
python -c "from ultralytics import YOLO; YOLO('yolo11n-seg.pt')"
```
-### Development Installation
-
-```bash
-pip install -e ".[dev]"
-```
+---
-## Usage
+## 6. Usage
-### One-Click Run
+### 6.1 One-Click Execution
```bash
-# Run complete workflow: setup + predict + voxelize
+# Complete safety validation workflow
./run.sh
-# Or with custom input
+# Custom input directory
./run.sh --input /path/to/images
-# Force pipeline mode for multiple images
+# Force pipeline mode for batch processing
./run.sh --pipeline
```
-### CLI Commands
+### 6.2 CLI Commands
```bash
# Environment setup and model download
aylm setup --download
-# Run SHARP prediction (image to 3D Gaussian)
+# Stage 1: SHARP 3D Reconstruction
aylm predict -i inputs/input_images -o outputs/output_gaussians -v
-# Run voxelization (3D Gaussian to voxel grid)
+# Stage 2: Voxelization (Occupancy 2.0)
aylm voxelize -i outputs/output_gaussians --voxel-size 0.005
-# Run complete pipeline (sequential)
+# Stage 3: Complete Pipeline (Sequential)
aylm process -v
-# Run parallel pipeline (for multiple images)
+# Stage 4: Parallel Pipeline (Multi-image)
aylm pipeline -i inputs/input_images -o outputs/output_gaussians -v
```
-### Video Processing
+### 6.3 Video Processing
```bash
-# Extract frames from video
+# Frame extraction
aylm video extract -i video.mp4 -o frames/ --interval 1.0
-# Process video (extract + inference + voxelize)
+# Full video pipeline (extract + inference + voxelize + tracking)
aylm video process -i video.mp4 -o output/ --use-gpu
-# Play voxel sequence
+# Visualization playback
aylm video play -i voxels/ --fps 10 --loop
```
-### Python API
+### 6.4 Python API
```python
from aylm.tools.pointcloud_voxelizer import PointCloudVoxelizer, VoxelizerConfig
from aylm.tools.semantic_fusion import SemanticFusion
from aylm.tools.object_detector import ObjectDetector
+from aylm.tools.pipeline_processor import PipelineProcessor, PipelineConfig
-# Basic voxelization
-config = VoxelizerConfig(voxel_size=0.005) # 5mm voxels
-processor = PointCloudVoxelizer(config=config)
-processor.process(
- input_path="output.ply",
+# Stage 1: Voxelization with Occupancy 2.0
+config = VoxelizerConfig(voxel_size=0.005) # 5mm resolution
+voxelizer = PointCloudVoxelizer(config=config)
+voxelizer.process(
+ input_path="gaussians.ply",
output_path="voxelized.ply",
- remove_ground=True
+ remove_ground=True # RANSAC ground plane removal
)
-# Semantic fusion with obstacle detection
-detector = ObjectDetector()
+# Stage 2: Semantic Fusion (2D → 3D Projection)
+detector = ObjectDetector(model_name="yolo11n-seg.pt")
detections = detector.detect("image.png")
fusion = SemanticFusion()
@@ -143,156 +521,180 @@ semantic_pc = fusion.fuse_semantics(
detections=detections
)
-# Save navigation mesh (5cm voxels)
-fusion.save_navigation_ply(semantic_pc, "navigation.ply", voxel_size=0.05)
-```
+# Stage 3: Safety Validation Output
+fusion.save_navigation_ply(semantic_pc, "safety_validation.ply", voxel_size=0.05)
-### Pipeline Processing
-
-```python
-from aylm.tools.pipeline_processor import PipelineProcessor, PipelineConfig
-
-# Configure pipeline
-config = PipelineConfig(
+# Full Pipeline Processing
+pipeline_config = PipelineConfig(
verbose=True,
enable_semantic_fusion=True,
output_navigation_ply=True
)
-
-# Process multiple images in parallel
-processor = PipelineProcessor(config)
+processor = PipelineProcessor(pipeline_config)
results = processor.run_pipeline(
input_dir="inputs/input_images",
output_dir="outputs"
)
```
-## Project Structure
+---
-```
-A.YLM/
-├── src/aylm/ # Main package
-│ ├── cli.py # Command line interface
-│ └── tools/ # Processing tools
-│ ├── pointcloud_voxelizer.py # Voxelization module
-│ ├── pipeline_processor.py # Parallel pipeline
-│ ├── video_pipeline.py # Video processing
-│ ├── semantic_fusion.py # Semantic labeling
-│ ├── object_detector.py # YOLO detection
-│ ├── obstacle_marker.py # Obstacle marking
-│ ├── pointcloud_slicer.py # Point cloud slicing
-│ └── coordinate_utils.py # Coordinate transforms
-├── ml-sharp/ # Apple SHARP model (submodule)
-├── models/ # Model checkpoints
-├── inputs/
-│ ├── input_images/ # Input images
-│ └── videos/ # Input videos
-├── outputs/
-│ ├── output_gaussians/ # 3D Gaussian PLY files
-│ ├── voxelized/ # Voxelized point clouds
-│ ├── detections/ # Detection results (JSON)
-│ └── navigation/ # Navigation meshes
-├── tests/ # Test suite
-├── run.sh # One-click run script
-└── pyproject.toml # Project configuration
-```
+## 7. Output Specification
-## Output Files
+### 7.1 File Formats
-| File | Description |
-|------|-------------|
-| `*.ply` | 3D Gaussian Splatting model from SHARP |
-| `vox_*.ply` | Voxelized point cloud with semantic colors |
-| `vox_*_obstacles.json` | Obstacle detection results |
-| `nav_*.ply` | Navigation mesh (5cm solid cubes) |
+| File Pattern | Format | Description |
+|--------------|--------|-------------|
+| `*.ply` | PLY | Raw 3DGS point cloud from SHARP |
+| `vox_*.ply` | PLY | Voxelized point cloud with semantic colors |
+| `nav_*.ply` | PLY | Safety validation mesh (5cm solid cubes) |
+| `*_obstacles.json` | JSON | Structured obstacle data for safety validation |
-### Obstacle JSON Format
+### 7.2 Safety Validation JSON Schema
```json
{
- "coordinate_systems": {
- "cv": {"axes": "X右, Y下, Z前"},
- "robot": {"axes": "X前, Y左, Z上"}
+ "metadata": {
+ "timestamp": "2026-02-27T10:30:00Z",
+ "frame_id": 42,
+ "coordinate_systems": {
+ "cv": {"axes": "X-right, Y-down, Z-forward"},
+ "robot": {"axes": "X-forward, Y-left, Z-up (ENU)"}
+ }
},
"obstacles": [
{
- "type": "可运动障碍物",
- "category": "车辆",
+ "id": 1,
+ "type": "dynamic",
+ "category": "vehicle",
"center_cv": [10.32, 0.06, 0.21],
"center_robot": [0.21, -10.32, -0.06],
- "dimensions_cv": [7.52, 3.36, 2.26],
- "confidence": 0.93
+ "dimensions": [7.52, 3.36, 2.26],
+ "velocity": [2.5, 0.0, 0.1],
+ "ttc": 4.2,
+ "confidence": 0.93,
+ "safety_status": "warning"
}
- ]
+ ],
+ "safety_summary": {
+ "overall_status": "safe",
+ "collision_risk": 0.15,
+ "geometric_violations": []
+ }
}
```
-## Configuration
+### 7.3 Semantic Label Taxonomy
+
+| Label ID | Category | Color (RGB) | Motion Type |
+|----------|----------|-------------|-------------|
+| 0 | GROUND | (139, 69, 19) | Static |
+| 1 | VEHICLE | (0, 0, 255) | Dynamic |
+| 2 | PEDESTRIAN | (255, 0, 0) | Dynamic |
+| 3 | BICYCLE | (0, 255, 255) | Dynamic |
+| 4 | BUILDING | (128, 128, 128) | Static |
+| 5 | VEGETATION | (0, 255, 0) | Static |
+| 255 | UNKNOWN | (255, 255, 255) | Unknown |
-### Environment Variables
+---
+
+## 8. Configuration
+
+### 8.1 Environment Variables
```bash
-export AYLM_ROOT="/path/to/project" # Project root directory
-export INPUT_DIR="/path/to/images" # Input images directory
-export OUTPUT_DIR="/path/to/output" # Output directory
+export AYLM_ROOT="/path/to/project"
+export INPUT_DIR="/path/to/images"
+export OUTPUT_DIR="/path/to/output"
+export CUDA_VISIBLE_DEVICES="0" # GPU selection
```
-### Voxelizer Parameters
+### 8.2 Voxelizer Parameters
-| Parameter | Default | Description |
-|-----------|---------|-------------|
-| `voxel_size` | 0.005 | Voxel size in meters (5mm) |
-| `statistical_nb_neighbors` | 20 | Neighbors for outlier removal |
-| `ransac_distance_threshold` | 0.02 | RANSAC plane distance threshold |
-| `ground_normal_threshold` | 0.8 | Ground normal Y-component threshold |
+| Parameter | Default | Range | Description |
+|-----------|---------|-------|-------------|
+| `voxel_size` | 0.005 | 0.001-0.1 | Voxel resolution (meters) |
+| `statistical_nb_neighbors` | 20 | 5-50 | Outlier removal neighbors |
+| `ransac_distance_threshold` | 0.02 | 0.01-0.1 | RANSAC plane threshold |
+| `ground_normal_threshold` | 0.8 | 0.5-1.0 | Ground normal Y-component |
-### Pipeline Parameters
+### 8.3 Safety Validation Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
-| `DEFAULT_VOXEL_SIZE` | 0.05 | Navigation voxel size (5cm) |
-| `DEFAULT_SLICE_RADIUS` | 20.0 | Point cloud slice radius (meters) |
+| `DEFAULT_VOXEL_SIZE` | 0.05 | Safety validation voxel size (5cm) |
+| `DEFAULT_SLICE_RADIUS` | 20.0 | Processing radius (meters) |
| `DEFAULT_FOV_DEGREES` | 60.0 | Camera field of view |
| `DEFAULT_DENSITY_THRESHOLD` | 3 | Minimum points per voxel |
+| `TTC_WARNING_THRESHOLD` | 3.0 | Time-to-collision warning (seconds) |
+| `TTC_CRITICAL_THRESHOLD` | 1.5 | Time-to-collision critical (seconds) |
-### Video Config (YAML)
+---
+
+## 9. Project Structure
-```yaml
-# inputs/videos/video_config.yaml
-frame_extraction_method: interval # interval/uniform/keyframe
-frame_interval: 1.0 # seconds
-gpu_acceleration: auto # auto/cuda/mps/none
-output_format: png
```
+A.YLM/
+├── src/aylm/ # Core Package
+│ ├── cli.py # Command Line Interface
+│ └── tools/ # Processing Modules
+│ ├── pointcloud_voxelizer.py # Occupancy 2.0 Voxelization
+│ ├── pipeline_processor.py # Parallel Pipeline Orchestration
+│ ├── video_pipeline.py # Video Sequence Processing
+│ ├── semantic_fusion.py # 2D→3D Semantic Projection
+│ ├── object_detector.py # YOLO Instance Segmentation
+│ ├── obstacle_marker.py # DBSCAN Obstacle Clustering
+│ ├── pointcloud_slicer.py # Spatial ROI Extraction
+│ └── coordinate_utils.py # CV↔Robot Coordinate Transform
+├── ml-sharp/ # Apple SHARP Model (submodule)
+├── models/ # Model Checkpoints
+├── inputs/ # Input Data
+│ ├── input_images/ # RGB Images
+│ └── videos/ # Video Files
+├── outputs/ # Output Data
+│ ├── output_gaussians/ # 3DGS Point Clouds
+│ ├── voxelized/ # Occupancy Grids
+│ ├── detections/ # Detection Results
+│ └── safety_validation/ # Safety Validation Results
+├── tests/ # Test Suite
+├── run.sh # One-Click Execution Script
+└── pyproject.toml # Project Configuration
+```
+
+---
+
+## 10. Future Work
+
+### 10.1 Advanced Safety Learning
+Implementation of reinforcement learning for adaptive safety boundary optimization.
+
+### 10.2 Multi-Sensor Fusion
+Integration with LiDAR for enhanced geometric accuracy in safety-critical scenarios.
+
+### 10.3 Temporal Safety Reasoning
+Incorporation of temporal models for predictive safety validation.
-## Semantic Labels
+### 10.4 Federated Safety Learning
+Distributed learning across vehicle fleets for collective safety improvement.
-| Label | Color | Description |
-|-------|-------|-------------|
-| GROUND | Brown | Ground plane (removed for navigation) |
-| VEHICLE | Blue | Cars, trucks, buses |
-| PEDESTRIAN | Red | People |
-| BICYCLE | Cyan | Bicycles, motorcycles |
-| BUILDING | Gray | Static structures |
-| VEGETATION | Green | Trees, plants |
-| UNKNOWN | White | Unclassified points |
+---
-## Development
+## 11. Development
-### Running Tests
+### 11.1 Testing
```bash
-# Run all tests
+# Run full test suite
pytest tests/ -v
-# Run specific test
-pytest tests/unit/test_semantic_fusion.py -v
-
-# Run with coverage
+# Run with coverage report
pytest --cov=aylm --cov-report=html
+
+# Run specific module tests
+pytest tests/unit/test_semantic_fusion.py -v
```
-### Code Quality
+### 11.2 Code Quality
```bash
# Format code
@@ -306,53 +708,71 @@ ruff check src/aylm tests
mypy src/aylm
```
-## Troubleshooting
+---
-### Common Issues
+## 12. Troubleshooting
-1. **Open3D Installation Failed**
- - Use Python 3.11 (Open3D may not support Python 3.13)
- - The system will automatically fall back to numpy implementation
+| Issue | Cause | Solution |
+|-------|-------|----------|
+| Open3D installation failed | Python 3.13 incompatibility | Use Python 3.11 |
+| YOLO model not found | First-time download | Run `pip install ultralytics` |
+| SHARP model download failed | Network issue | Manual download from Apple CDN |
+| Out of memory | Large point cloud | Increase `voxel_size` or use `--slice-radius` |
+| CUDA/MPS unavailable | Driver issue | Check `torch.cuda.is_available()` |
-2. **YOLO Model Not Found**
- ```bash
- pip install ultralytics
- # Model auto-downloads on first use (~6MB)
- ```
+---
-3. **Model Download Failed**
- - Check network connection
- - Model URL: `https://ml-site.cdn-apple.com/models/sharp/sharp_2572gikvuh.pt`
- - Model size: ~2.8GB
+## License
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
-4. **Out of Memory**
- - Reduce input image resolution
- - Increase voxel size (e.g., 0.01 for 1cm voxels)
- - Use `--slice-radius` to limit processing area
+## Acknowledgments
-5. **CUDA/MPS Issues**
- ```bash
- # Check PyTorch device availability
- python -c "import torch; print(torch.cuda.is_available(), torch.backends.mps.is_available())"
- ```
+- **Apple Inc.** - SHARP Vision Transformer model and research
+- **Ultralytics** - YOLO object detection framework
+- **Open3D Community** - Point cloud processing library
+- **PyTorch Team** - Deep learning framework
-## License
+## Citation
-This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+If you use A-YLM in your research, please cite:
+
+```bibtex
+@software{aylm2026,
+ title={A-YLM: Geometric Constitutional AI for Embodied Intelligence},
+ author={TRIP (appergb)},
+ year={2026},
+ url={https://github.com/appergb/A.YLM},
+ note={Self-supervised safety framework extending Constitutional AI to physical world}
+}
+```
+
+---
## Acknowledgments
-- **Apple Inc.** - SHARP model and research
-- **Ultralytics** - YOLO object detection
+- **Anthropic** - Constitutional AI paradigm that inspired this work
+- **Apple Inc.** - SHARP Vision Transformer model and research
+- **Ultralytics** - YOLO object detection framework
- **Open3D Community** - Point cloud processing library
- **PyTorch Team** - Deep learning framework
-## Author
+---
-**TRIP** (appergb)
+## Contact & Community
-**Contributors**: closer, true
+- **Author**: TRIP (appergb)
+- **Contributors**: Claude Code, Junie
+- **Project Status**: Production Ready
+- **Issues**: [GitHub Issues](https://github.com/appergb/A.YLM/issues)
+- **Discussions**: [GitHub Discussions](https://github.com/appergb/A.YLM/discussions)
---
-**Project Status**: Production Ready
+
+
+**Star us on GitHub if you find this project useful!**
+
+*A-YLM: Teaching AI to understand the physical world through geometric constitution*
+
+
diff --git a/configs/constitution_example.yaml b/configs/constitution_example.yaml
new file mode 100644
index 0000000..b2828fc
--- /dev/null
+++ b/configs/constitution_example.yaml
@@ -0,0 +1,57 @@
+# A-YLM 宪法配置示例
+# 用户可以根据自己的需求修改此配置
+
+# 宪法原则列表
+principles:
+ # 碰撞检测 - 最高优先级
+ - name: no_collision
+ severity: critical
+ enabled: true
+ params:
+ check_trajectory: true
+ prediction_horizon: 2.0 # 预测时间范围(秒)
+
+ # TTC 安全 - 高优先级
+ - name: ttc_safety
+ severity: high
+ enabled: true
+ params:
+ warning_threshold: 3.0 # 警告阈值(秒)
+ critical_threshold: 1.5 # 关键阈值(秒)
+
+ # 安全跟车距离
+ - name: safe_following
+ severity: high
+ enabled: true
+ params:
+ time_gap: 2.0 # 时间间隔(秒)
+
+ # 车道合规
+ - name: lane_compliance
+ severity: medium
+ enabled: true
+
+ # 速度限制
+ - name: speed_limit
+ severity: medium
+ enabled: true
+ params:
+ max_speed: 120 # km/h
+
+# 安全阈值
+thresholds:
+ ttc_warning: 3.0 # TTC 警告阈值(秒)
+ ttc_critical: 1.5 # TTC 关键阈值(秒)
+ min_safe_distance: 2.0 # 最小安全距离(米)
+ speed_distance_factor: 0.5 # 速度-距离系数
+
+# 打分权重
+weights:
+ collision: 1.0 # 碰撞权重
+ ttc: 0.8 # TTC 权重
+ boundary: 0.5 # 边界权重
+
+# 训练信号配置
+training:
+ generate_positive_signals: false # 是否生成正样本
+ export_format: json # 导出格式: json, tfrecord, parquet
diff --git a/fuse.sh b/fuse.sh
new file mode 100755
index 0000000..5ed387f
--- /dev/null
+++ b/fuse.sh
@@ -0,0 +1,110 @@
+#!/bin/bash
+# 多帧点云融合脚本
+# 用法: ./fuse.sh [输入目录] [输出文件]
+
+set -e
+
+# 颜色定义
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+# 默认路径
+DEFAULT_INPUT="outputs/video_output/voxelized"
+DEFAULT_OUTPUT="outputs/video_output/fused_map.ply"
+
+# 解析参数
+INPUT_DIR="${1:-$DEFAULT_INPUT}"
+OUTPUT_FILE="${2:-$DEFAULT_OUTPUT}"
+
+# 显示帮助
+if [[ "$1" == "-h" || "$1" == "--help" ]]; then
+ echo "多帧点云融合脚本"
+ echo ""
+ echo "用法: ./fuse.sh [输入目录] [输出文件] [选项]"
+ echo ""
+ echo "参数:"
+ echo " 输入目录 包含 vox_*.ply 文件的目录 (默认: $DEFAULT_INPUT)"
+ echo " 输出文件 融合后的点云文件路径 (默认: $DEFAULT_OUTPUT)"
+ echo ""
+ echo "选项:"
+ echo " -h, --help 显示帮助"
+ echo ""
+ echo "示例:"
+ echo " ./fuse.sh # 使用默认路径"
+ echo " ./fuse.sh outputs/my_scan # 指定输入目录"
+ echo " ./fuse.sh outputs/my_scan my_map.ply # 指定输入和输出"
+ echo ""
+ echo "高级用法 (直接调用 aylm):"
+ echo " aylm fuse -i 输入目录 -o 输出文件 --icp-distance 0.1 --voxel-size 0.05"
+ exit 0
+fi
+
+# 检查输入目录
+if [[ ! -d "$INPUT_DIR" ]]; then
+ echo -e "${YELLOW}错误: 输入目录不存在: $INPUT_DIR${NC}"
+ exit 1
+fi
+
+# 统计点云文件
+PLY_COUNT=$(find "$INPUT_DIR" -name "vox_*.ply" 2>/dev/null | wc -l | tr -d ' ')
+
+if [[ "$PLY_COUNT" -eq 0 ]]; then
+ echo -e "${YELLOW}错误: 未找到 vox_*.ply 文件${NC}"
+ echo "目录: $INPUT_DIR"
+ exit 1
+fi
+
+# 显示信息
+echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+echo -e "${GREEN}多帧点云融合${NC}"
+echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+echo ""
+echo -e "输入目录: ${GREEN}$INPUT_DIR${NC}"
+echo -e "点云数量: ${GREEN}$PLY_COUNT${NC} 帧"
+echo -e "输出文件: ${GREEN}$OUTPUT_FILE${NC}"
+echo ""
+
+# 列出点云文件
+echo "点云文件:"
+find "$INPUT_DIR" -name "vox_*.ply" | sort | while read f; do
+ SIZE=$(du -h "$f" | cut -f1)
+ echo " $(basename "$f") ($SIZE)"
+done
+echo ""
+
+# 激活虚拟环境
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+if [[ -d "$SCRIPT_DIR/aylm_env" ]]; then
+ source "$SCRIPT_DIR/aylm_env/bin/activate"
+fi
+
+# 执行融合
+echo -e "${BLUE}开始融合...${NC}"
+echo ""
+
+aylm fuse -i "$INPUT_DIR" -o "$OUTPUT_FILE" -v
+
+# 检查结果
+if [[ -f "$OUTPUT_FILE" ]]; then
+ echo ""
+ echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+ echo -e "${GREEN}融合完成!${NC}"
+ echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+
+ OUTPUT_SIZE=$(du -h "$OUTPUT_FILE" | cut -f1)
+ echo ""
+ echo -e "输出文件: ${GREEN}$OUTPUT_FILE${NC} ($OUTPUT_SIZE)"
+
+ # 检查位姿文件
+ POSES_FILE="${OUTPUT_FILE%.ply}.poses.json"
+ if [[ -f "$POSES_FILE" ]]; then
+ echo -e "位姿轨迹: ${GREEN}$POSES_FILE${NC}"
+ fi
+
+ echo ""
+ echo "查看结果:"
+ echo " open $OUTPUT_FILE # macOS 默认应用打开"
+ echo " meshlab $OUTPUT_FILE # 使用 MeshLab 查看"
+fi
diff --git a/pyproject.toml b/pyproject.toml
index 5540449..a03fb7d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "aylm"
version = "2.0.0"
-description = "AYLM - Advanced YLM for 3D Gaussian Splatting"
+description = "A-YLM: Geometric Constitutional AI for Embodied Intelligence - Bidirectional fusion with E2E driving systems (FSD/ADS), providing 3D point cloud input and safety supervision. Self-supervised learning without human annotation. Edge-deployable real-time geometric validation."
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.9"
@@ -20,6 +20,8 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
+ "Topic :: Scientific/Engineering :: Image Recognition",
+ "Topic :: Scientific/Engineering :: Visualization",
]
dependencies = [
"numpy",
@@ -41,9 +43,11 @@ semantic = [
]
dev = [
"pytest",
+ "pytest-cov",
"black",
"isort",
"ruff",
+ "mypy",
]
all = [
"open3d>=0.18.0",
@@ -68,3 +72,47 @@ line_length = 88
[tool.ruff]
line-length = 88
target-version = "py39"
+
+[tool.ruff.lint]
+select = [
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # Pyflakes
+ "I", # isort
+ "B", # flake8-bugbear
+ "C4", # flake8-comprehensions
+ "UP", # pyupgrade
+ "SIM", # flake8-simplify
+ "TCH", # flake8-type-checking
+ "RUF", # Ruff-specific rules
+]
+ignore = [
+ "E501", # line too long (handled by black)
+ "B008", # function call in default argument
+ "B905", # zip without strict
+ "RUF001", # ambiguous unicode in string (中文逗号等)
+ "RUF002", # ambiguous unicode in docstring
+ "RUF003", # ambiguous unicode in comment
+ "RUF022", # __all__ not sorted (按功能分组更清晰)
+ "TC002", # move import into TYPE_CHECKING (运行时需要)
+]
+
+[tool.ruff.lint.isort]
+known-first-party = ["aylm"]
+
+[tool.mypy]
+python_version = "3.11"
+warn_return_any = false
+warn_unused_configs = true
+disallow_untyped_defs = false
+check_untyped_defs = false
+ignore_missing_imports = true
+exclude = [
+ "tests/",
+ "ml-sharp/",
+ "aylm_env/",
+]
+
+[[tool.mypy.overrides]]
+module = "yaml"
+ignore_missing_imports = true
diff --git a/run.sh b/run.sh
index 39217ba..3213f15 100755
--- a/run.sh
+++ b/run.sh
@@ -62,6 +62,10 @@ ${YELLOW}点云切片选项:${NC}
--no-slice 禁用点云切片
--slice-radius 切片半径/米 (默认: 10.0)
+${YELLOW}目标跟踪选项:${NC}
+ --track 启用目标跟踪 (默认)
+ --no-track 禁用目标跟踪
+
${YELLOW}示例:${NC}
./run.sh --setup # 初始化
./run.sh -i ./images # 处理图像
@@ -158,9 +162,10 @@ run_auto() {
main() {
local action="auto" input_dir="inputs/input_images" output_dir="" config_file=""
local extra_args=() check_only=false use_gpu=false frame_interval="" fps="10" loop=false
- # 语义检测和切片参数(默认都启用)
- local semantic=true semantic_model="yolo11n-seg.pt" semantic_confidence="0.5"
+ # 语义检测、切片和跟踪参数(默认都启用)
+ local semantic=true semantic_model="yolo11n-seg.pt" semantic_confidence="0.25"
local slice=true slice_radius="10.0"
+ local track=true
while [[ $# -gt 0 ]]; do
case "$1" in
@@ -185,6 +190,9 @@ main() {
--slice) slice=true; shift ;;
--no-slice) slice=false; shift ;;
--slice-radius) slice_radius="$2"; shift 2 ;;
+ # 跟踪参数
+ --track) track=true; shift ;;
+ --no-track) track=false; shift ;;
*) extra_args+=("$1"); shift ;;
esac
done
@@ -213,6 +221,13 @@ main() {
slice_args+=("--no-slice")
fi
+ local track_args=()
+ if [[ "$track" == true ]]; then
+ track_args+=("--track")
+ else
+ track_args+=("--no-track")
+ fi
+
case "$action" in
setup) log_step "下载模型..."; python3 -m aylm.cli setup --download ;;
voxelize) run_voxelize "${extra_args[@]}" ;;
@@ -223,6 +238,7 @@ main() {
local video_args=("${extra_args[@]}")
[[ -n "$config_file" ]] && video_args+=("-c" "$config_file")
[[ "$use_gpu" == true ]] && video_args+=("--use-gpu")
+ video_args+=("${semantic_args[@]}" "${slice_args[@]}" "${track_args[@]}")
show_banner "视频处理"
run_video_process "${video_args[@]}" ;;
video-extract)
@@ -234,7 +250,7 @@ main() {
local play_args=("-i" "$input_dir" "--fps" "$fps")
[[ "$loop" == true ]] && play_args+=("--loop")
run_video_play "${play_args[@]}" ;;
- auto) run_auto "$input_dir" "${extra_args[@]}" "${semantic_args[@]}" "${slice_args[@]}" ;;
+ auto) run_auto "$input_dir" "${extra_args[@]}" "${semantic_args[@]}" "${slice_args[@]}" "${track_args[@]}" ;;
esac
log_info "完成"
diff --git a/src/aylm/cli.py b/src/aylm/cli.py
index 60a2da1..49900c3 100644
--- a/src/aylm/cli.py
+++ b/src/aylm/cli.py
@@ -283,7 +283,18 @@ def cmd_video_process(args: argparse.Namespace) -> int:
from aylm.tools.video_config import load_or_create_config
from aylm.tools.video_types import FrameExtractionMethod, GPUAcceleration
- config = load_or_create_config(Path(args.config) if args.config else None)
+ # 确定配置文件路径:优先使用命令行指定,否则查找视频目录下的配置
+ config_path = None
+ if args.config:
+ config_path = Path(args.config)
+ else:
+ # 自动查找视频所在目录的配置文件
+ video_dir_config = video_path.parent / "video_config.yaml"
+ if video_dir_config.exists():
+ config_path = video_dir_config
+ print(f" 发现配置文件: {config_path}")
+
+ config = load_or_create_config(config_path)
if args.frame_interval:
config.frame_interval = args.frame_interval
config.frame_extraction_method = FrameExtractionMethod.INTERVAL
@@ -317,6 +328,14 @@ def cmd_video_process(args: argparse.Namespace) -> int:
# 解析语义检测选项
enable_semantic = args.semantic and not getattr(args, "no_semantic", False)
+ # 解析切片选项
+ enable_slice = getattr(args, "slice", True) and not getattr(
+ args, "no_slice", False
+ )
+ # 解析跟踪选项
+ enable_tracking = getattr(args, "track", True) and not getattr(
+ args, "no_track", False
+ )
pipeline_config = VideoPipelineConfig(
video_config=config,
@@ -326,6 +345,9 @@ def cmd_video_process(args: argparse.Namespace) -> int:
enable_semantic=enable_semantic,
semantic_model=args.semantic_model,
semantic_confidence=args.semantic_confidence,
+ enable_slice=enable_slice,
+ slice_radius=getattr(args, "slice_radius", 10.0),
+ enable_tracking=enable_tracking,
)
stats = VideoPipelineProcessor(pipeline_config).process(video_path, output_dir)
@@ -370,7 +392,18 @@ def cmd_video_extract(args: argparse.Namespace) -> int:
from aylm.tools.video_config import load_or_create_config
from aylm.tools.video_types import FrameExtractionMethod
- config = load_or_create_config(Path(args.config) if args.config else None)
+ # 确定配置文件路径:优先使用命令行指定,否则查找视频目录下的配置
+ config_path = None
+ if args.config:
+ config_path = Path(args.config)
+ else:
+ # 自动查找视频所在目录的配置文件
+ video_dir_config = video_path.parent / "video_config.yaml"
+ if video_dir_config.exists():
+ config_path = video_dir_config
+ print(f" 发现配置文件: {config_path}")
+
+ config = load_or_create_config(config_path)
if args.frame_interval:
config.frame_interval = args.frame_interval
config.frame_extraction_method = FrameExtractionMethod.INTERVAL
@@ -538,6 +571,62 @@ def cmd_pipeline(args: argparse.Namespace) -> int:
return 1
+def cmd_fuse(args: argparse.Namespace) -> int:
+ """多帧点云配准融合."""
+ input_dir = Path(args.input)
+ if not input_dir.exists():
+ logger.error(f"输入目录不存在: {input_dir}")
+ return 1
+
+ output_path = Path(args.output) if args.output else input_dir / "fused_map.ply"
+
+ if args.verbose:
+ logging.getLogger("aylm").setLevel(logging.DEBUG)
+
+ print(f"AYLM v{get_version()} - 多帧点云融合\n")
+ print(f"[输入] {input_dir}")
+ print(f"[模式] {args.pattern}")
+ print("[配置]")
+ print(f" ICP距离: {args.icp_distance}m")
+ print(f" 体素大小: {args.voxel_size}m")
+ print(f"[输出] {output_path}")
+ print("=" * 50)
+
+ try:
+ from aylm.tools.multiframe_fusion import MultiframeFusion, RegistrationConfig
+
+ config = RegistrationConfig(
+ icp_max_correspondence_distance=args.icp_distance,
+ fusion_voxel_size=args.voxel_size,
+ )
+
+ fusion = MultiframeFusion(config)
+ result = fusion.fuse_from_directory(input_dir, args.pattern)
+
+ if len(result.fused_pointcloud.points) == 0:
+ logger.error("融合失败,未生成有效点云")
+ return 1
+
+ fusion.save_fused_map(result, output_path)
+
+ print("\n" + "=" * 50)
+ print("[完成] 多帧融合结束")
+ print(f" 总帧数: {result.total_frames}")
+ print(f" 成功配准: {result.successful_registrations}")
+ print(f" 融合点数: {len(result.fused_pointcloud.points)}")
+ print(f" 耗时: {result.fusion_time:.2f}s")
+
+ return 0
+
+ except ImportError as e:
+ logger.error(f"导入融合模块失败: {e}")
+ logger.error("请确保已安装 Open3D: pip install open3d")
+ return 1
+ except Exception as e:
+ logger.error(f"融合失败: {e}")
+ return 1
+
+
def create_parser() -> argparse.ArgumentParser:
"""创建命令行解析器."""
parser = argparse.ArgumentParser(
@@ -585,7 +674,7 @@ def create_parser() -> argparse.ArgumentParser:
)
p.add_argument("--no-semantic", action="store_true", help="禁用语义检测")
p.add_argument("--semantic-model", default="yolo11n-seg.pt", help="YOLO模型名称")
- p.add_argument("--semantic-confidence", type=float, default=0.5, help="检测置信度")
+ p.add_argument("--semantic-confidence", type=float, default=0.25, help="检测置信度")
# 切片选项
p.add_argument("--slice", action="store_true", default=True, help="启用点云切片")
p.add_argument("--no-slice", action="store_true", help="禁用点云切片")
@@ -618,7 +707,14 @@ def create_parser() -> argparse.ArgumentParser:
p.add_argument("--semantic", action="store_true", default=True, help="启用语义检测")
p.add_argument("--no-semantic", action="store_true", help="禁用语义检测")
p.add_argument("--semantic-model", default="yolo11n-seg.pt", help="YOLO模型名称")
- p.add_argument("--semantic-confidence", type=float, default=0.5, help="检测置信度")
+ p.add_argument("--semantic-confidence", type=float, default=0.25, help="检测置信度")
+ # 点云切片选项
+ p.add_argument("--slice", action="store_true", default=True, help="启用点云切片")
+ p.add_argument("--no-slice", action="store_true", help="禁用点云切片")
+ p.add_argument("--slice-radius", type=float, default=10.0, help="切片半径(米)")
+ # 目标跟踪选项
+ p.add_argument("--track", action="store_true", default=True, help="启用目标跟踪")
+ p.add_argument("--no-track", action="store_true", help="禁用目标跟踪")
p.add_argument("-v", "--verbose", action="store_true", help="详细输出")
p.set_defaults(func=cmd_video_process)
@@ -639,6 +735,18 @@ def create_parser() -> argparse.ArgumentParser:
p.add_argument("-v", "--verbose", action="store_true", help="详细输出")
p.set_defaults(func=cmd_video_play)
+ # fuse - 多帧点云融合
+ p = subs.add_parser("fuse", help="多帧点云配准融合")
+ p.add_argument("-i", "--input", required=True, help="输入点云目录")
+ p.add_argument("-o", "--output", help="输出融合地图路径")
+ p.add_argument("--pattern", default="vox_*.ply", help="文件匹配模式")
+ p.add_argument(
+ "--icp-distance", type=float, default=0.05, help="ICP最大对应距离(米)"
+ )
+ p.add_argument("--voxel-size", type=float, default=0.02, help="融合后体素大小(米)")
+ p.add_argument("-v", "--verbose", action="store_true", help="详细输出")
+ p.set_defaults(func=cmd_fuse)
+
return parser
diff --git a/src/aylm/constitution/__init__.py b/src/aylm/constitution/__init__.py
new file mode 100644
index 0000000..91c0851
--- /dev/null
+++ b/src/aylm/constitution/__init__.py
@@ -0,0 +1,38 @@
+"""A-YLM 几何宪法式 AI 核心模块。
+
+本模块提供开放式接口,允许用户自定义:
+- 宪法原则(ConstitutionPrinciple)
+- 安全打分器(SafetyScorer)
+- 训练信号生成器(TrainingSignalGenerator)
+
+设计理念:
+- 我们提供基础设施和接口定义
+- 具体的打分逻辑和训练方式由用户根据需求实现
+- 支持插件机制,便于第三方扩展
+"""
+
+from .base import (
+ ConstitutionPrinciple,
+ Severity,
+ ViolationResult,
+)
+from .config import ConstitutionConfig
+from .registry import ConstitutionRegistry
+from .scorer import SafetyScore, SafetyScorer
+from .training import TrainingSignal, TrainingSignalGenerator
+
+__all__ = [
+ # 基础类型
+ "Severity",
+ "ViolationResult",
+ # 抽象基类
+ "ConstitutionPrinciple",
+ "SafetyScorer",
+ "TrainingSignalGenerator",
+ # 数据类
+ "SafetyScore",
+ "TrainingSignal",
+ "ConstitutionConfig",
+ # 注册机制
+ "ConstitutionRegistry",
+]
diff --git a/src/aylm/constitution/base.py b/src/aylm/constitution/base.py
new file mode 100644
index 0000000..392b489
--- /dev/null
+++ b/src/aylm/constitution/base.py
@@ -0,0 +1,133 @@
+"""宪法原则基类定义。
+
+本模块定义了几何宪法式 AI 的核心抽象:
+- Severity: 违规严重性等级
+- ViolationResult: 违规检测结果
+- ConstitutionPrinciple: 宪法原则基类
+
+用户可以继承 ConstitutionPrinciple 实现自定义的安全规则。
+"""
+
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from enum import Enum
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+ from .types import AIDecision, SceneState
+
+
+class Severity(Enum):
+ """违规严重性等级。
+
+ 用于定义宪法原则被违反时的严重程度,
+ 影响最终安全分数的计算和推荐动作。
+ """
+
+ CRITICAL = 1.0 # 关键:必须立即阻止(如即将碰撞)
+ HIGH = 0.8 # 高:强烈警告,建议干预
+ MEDIUM = 0.5 # 中:一般警告,需要注意
+ LOW = 0.2 # 低:轻微提示,可忽略
+
+
+@dataclass
+class ViolationResult:
+ """违规检测结果。
+
+ Attributes:
+ violated: 是否违反原则
+ severity: 违规严重性
+ confidence: 检测置信度 (0.0-1.0)
+ description: 违规描述(人类可读)
+ metrics: 相关度量值(如距离、TTC 等)
+ correction_hint: 纠正建议(可选,用于生成训练信号)
+ """
+
+ violated: bool
+ severity: Severity
+ confidence: float
+ description: str
+ metrics: dict[str, float] = field(default_factory=dict)
+ correction_hint: dict[str, Any] | None = None
+
+ def to_dict(self) -> dict[str, Any]:
+ """转换为字典格式。"""
+ return {
+ "violated": self.violated,
+ "severity": self.severity.name,
+ "severity_weight": self.severity.value,
+ "confidence": self.confidence,
+ "description": self.description,
+ "metrics": self.metrics,
+ "correction_hint": self.correction_hint,
+ }
+
+
+class ConstitutionPrinciple(ABC):
+ """宪法原则基类。
+
+ 所有安全规则都应继承此类并实现 evaluate 方法。
+ 这是 A-YLM 几何宪法式 AI 的核心抽象。
+
+ Example:
+ >>> class NoCollisionPrinciple(ConstitutionPrinciple):
+ ... @property
+ ... def name(self) -> str:
+ ... return "no_collision"
+ ...
+ ... @property
+ ... def severity(self) -> Severity:
+ ... return Severity.CRITICAL
+ ...
+ ... def evaluate(self, state, decision) -> ViolationResult:
+ ... # 实现碰撞检测逻辑
+ ... collision = check_collision(state.obstacles, decision.trajectory)
+ ... return ViolationResult(
+ ... violated=collision,
+ ... severity=self.severity,
+ ... confidence=0.95,
+ ... description="检测到碰撞风险" if collision else "安全",
+ ... )
+ """
+
+ @property
+ @abstractmethod
+ def name(self) -> str:
+ """原则名称(唯一标识符)。"""
+ pass
+
+ @property
+ @abstractmethod
+ def severity(self) -> Severity:
+ """默认严重性等级。"""
+ pass
+
+ @property
+ def description(self) -> str:
+ """原则描述(可选覆盖)。"""
+ return f"宪法原则: {self.name}"
+
+ @property
+ def enabled(self) -> bool:
+ """是否启用(可动态控制)。"""
+ return True
+
+ @abstractmethod
+ def evaluate(
+ self,
+ state: "SceneState",
+ decision: "AIDecision",
+ ) -> ViolationResult:
+ """评估 AI 决策是否违反此原则。
+
+ Args:
+ state: 当前场景状态(包含障碍物、自车状态等)
+ decision: AI 的决策(包含规划轨迹、控制指令等)
+
+ Returns:
+ ViolationResult: 违规检测结果
+ """
+ pass
+
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}(name={self.name}, severity={self.severity.name})"
diff --git a/src/aylm/constitution/config.py b/src/aylm/constitution/config.py
new file mode 100644
index 0000000..2974050
--- /dev/null
+++ b/src/aylm/constitution/config.py
@@ -0,0 +1,188 @@
+"""宪法配置模块。
+
+支持从 YAML/JSON 文件加载宪法配置,便于用户自定义规则。
+"""
+
+import json
+from dataclasses import dataclass, field
+from pathlib import Path
+from typing import Any
+
+
+@dataclass
+class PrincipleConfig:
+ """单个宪法原则配置。"""
+
+ name: str
+ severity: str = "medium" # critical, high, medium, low
+ enabled: bool = True
+ params: dict[str, Any] = field(default_factory=dict)
+
+
+@dataclass
+class ConstitutionConfig:
+ """宪法配置。
+
+ 支持从 YAML/JSON 文件加载,便于用户自定义安全规则。
+
+ Example YAML:
+ ```yaml
+ principles:
+ - name: no_collision
+ severity: critical
+ enabled: true
+ - name: ttc_safety
+ severity: high
+ params:
+ warning_threshold: 3.0
+ critical_threshold: 1.5
+
+ thresholds:
+ min_safe_distance: 2.0
+ speed_distance_factor: 0.5
+
+ training:
+ generate_positive_signals: false
+ export_format: json
+ ```
+ """
+
+ # 宪法原则列表
+ principles: list[PrincipleConfig] = field(default_factory=list)
+
+ # 安全阈值
+ ttc_warning_threshold: float = 3.0 # TTC 警告阈值(秒)
+ ttc_critical_threshold: float = 1.5 # TTC 关键阈值(秒)
+ min_safe_distance: float = 2.0 # 最小安全距离(米)
+ speed_distance_factor: float = 0.5 # 速度-距离系数
+
+ # 训练信号配置
+ generate_positive_signals: bool = False # 是否生成正样本
+ signal_export_format: str = "json" # 导出格式
+
+ # 打分权重
+ collision_weight: float = 1.0
+ ttc_weight: float = 0.8
+ boundary_weight: float = 0.5
+
+ def __post_init__(self):
+ """初始化默认原则(如果未指定)。"""
+ if not self.principles:
+ self.principles = self._default_principles()
+
+ @staticmethod
+ def _default_principles() -> list[PrincipleConfig]:
+ """默认宪法原则。"""
+ return [
+ PrincipleConfig(name="no_collision", severity="critical"),
+ PrincipleConfig(name="safe_following", severity="high"),
+ PrincipleConfig(name="ttc_safety", severity="high"),
+ PrincipleConfig(name="lane_compliance", severity="medium"),
+ PrincipleConfig(name="speed_limit", severity="medium"),
+ ]
+
+ @classmethod
+ def from_dict(cls, data: dict[str, Any]) -> "ConstitutionConfig":
+ """从字典创建配置。"""
+ principles = []
+ for p in data.get("principles", []):
+ principles.append(
+ PrincipleConfig(
+ name=p["name"],
+ severity=p.get("severity", "medium"),
+ enabled=p.get("enabled", True),
+ params=p.get("params", {}),
+ )
+ )
+
+ thresholds = data.get("thresholds", {})
+ training = data.get("training", {})
+ weights = data.get("weights", {})
+
+ return cls(
+ principles=principles if principles else None,
+ ttc_warning_threshold=thresholds.get("ttc_warning", 3.0),
+ ttc_critical_threshold=thresholds.get("ttc_critical", 1.5),
+ min_safe_distance=thresholds.get("min_safe_distance", 2.0),
+ speed_distance_factor=thresholds.get("speed_distance_factor", 0.5),
+ generate_positive_signals=training.get("generate_positive_signals", False),
+ signal_export_format=training.get("export_format", "json"),
+ collision_weight=weights.get("collision", 1.0),
+ ttc_weight=weights.get("ttc", 0.8),
+ boundary_weight=weights.get("boundary", 0.5),
+ )
+
+ @classmethod
+ def from_json(cls, path: str | Path) -> "ConstitutionConfig":
+ """从 JSON 文件加载配置。"""
+ with open(path, encoding="utf-8") as f:
+ data = json.load(f)
+ return cls.from_dict(data)
+
+ @classmethod
+ def from_yaml(cls, path: str | Path) -> "ConstitutionConfig":
+ """从 YAML 文件加载配置。"""
+ try:
+ import yaml
+ except ImportError as e:
+ raise ImportError("需要安装 pyyaml: pip install pyyaml") from e
+
+ with open(path, encoding="utf-8") as f:
+ data = yaml.safe_load(f)
+ return cls.from_dict(data)
+
+ def to_dict(self) -> dict[str, Any]:
+ """转换为字典格式。"""
+ return {
+ "principles": [
+ {
+ "name": p.name,
+ "severity": p.severity,
+ "enabled": p.enabled,
+ "params": p.params,
+ }
+ for p in self.principles
+ ],
+ "thresholds": {
+ "ttc_warning": self.ttc_warning_threshold,
+ "ttc_critical": self.ttc_critical_threshold,
+ "min_safe_distance": self.min_safe_distance,
+ "speed_distance_factor": self.speed_distance_factor,
+ },
+ "training": {
+ "generate_positive_signals": self.generate_positive_signals,
+ "export_format": self.signal_export_format,
+ },
+ "weights": {
+ "collision": self.collision_weight,
+ "ttc": self.ttc_weight,
+ "boundary": self.boundary_weight,
+ },
+ }
+
+ def save_json(self, path: str | Path) -> None:
+ """保存为 JSON 文件。"""
+ with open(path, "w", encoding="utf-8") as f:
+ json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
+
+ def save_yaml(self, path: str | Path) -> None:
+ """保存为 YAML 文件。"""
+ try:
+ import yaml
+ except ImportError as e:
+ raise ImportError("需要安装 pyyaml: pip install pyyaml") from e
+
+ with open(path, "w", encoding="utf-8") as f:
+ yaml.dump(self.to_dict(), f, allow_unicode=True, default_flow_style=False)
+
+ def get_principle(self, name: str) -> PrincipleConfig | None:
+ """获取指定名称的原则配置。"""
+ for p in self.principles:
+ if p.name == name:
+ return p
+ return None
+
+ def is_principle_enabled(self, name: str) -> bool:
+ """检查原则是否启用。"""
+ p = self.get_principle(name)
+ return p.enabled if p else False
diff --git a/src/aylm/constitution/principles/__init__.py b/src/aylm/constitution/principles/__init__.py
new file mode 100644
index 0000000..3640bf4
--- /dev/null
+++ b/src/aylm/constitution/principles/__init__.py
@@ -0,0 +1,12 @@
+"""内置宪法原则实现。
+
+提供常用的宪法原则参考实现,用户可以直接使用或作为自定义实现的参考。
+"""
+
+from .collision import NoCollisionPrinciple
+from .ttc import TTCSafetyPrinciple
+
+__all__ = [
+ "NoCollisionPrinciple",
+ "TTCSafetyPrinciple",
+]
diff --git a/src/aylm/constitution/principles/collision.py b/src/aylm/constitution/principles/collision.py
new file mode 100644
index 0000000..736cef4
--- /dev/null
+++ b/src/aylm/constitution/principles/collision.py
@@ -0,0 +1,139 @@
+"""碰撞检测宪法原则。
+
+提供碰撞检测的参考实现,用户可以继承或修改。
+"""
+
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from ..base import ConstitutionPrinciple, Severity, ViolationResult
+from ..registry import ConstitutionRegistry
+
+if TYPE_CHECKING:
+ from ..types import AIDecision, SceneState
+
+
+@ConstitutionRegistry.register_principle("no_collision")
+class NoCollisionPrinciple(ConstitutionPrinciple):
+ """无碰撞原则。
+
+ 检测 AI 决策轨迹是否与障碍物发生碰撞。
+
+ 数学表述:
+ ∀t: V_ego(t) ∩ V_obstacle(t) = ∅
+
+ 参数:
+ safety_margin: 安全边距(米)
+ prediction_horizon: 预测时间范围(秒)
+ """
+
+ def __init__(
+ self,
+ safety_margin: float = 0.5,
+ prediction_horizon: float = 2.0,
+ ):
+ self.safety_margin = safety_margin
+ self.prediction_horizon = prediction_horizon
+
+ @property
+ def name(self) -> str:
+ return "no_collision"
+
+ @property
+ def severity(self) -> Severity:
+ return Severity.CRITICAL
+
+ @property
+ def description(self) -> str:
+ return "检测 AI 决策是否会导致碰撞"
+
+ def evaluate(
+ self,
+ state: "SceneState",
+ decision: "AIDecision",
+ ) -> ViolationResult:
+ """评估碰撞风险。
+
+ Args:
+ state: 场景状态(包含障碍物列表)
+ decision: AI 决策(包含规划轨迹)
+
+ Returns:
+ ViolationResult: 违规检测结果
+ """
+ if not state.obstacles:
+ return ViolationResult(
+ violated=False,
+ severity=self.severity,
+ confidence=1.0,
+ description="无障碍物",
+ )
+
+ if not decision.trajectory:
+ return ViolationResult(
+ violated=False,
+ severity=self.severity,
+ confidence=0.5,
+ description="无轨迹信息,无法评估",
+ )
+
+ # 检测轨迹与障碍物的最小距离
+ min_distance = float("inf")
+ collision_obstacle = None
+
+ for traj_point in decision.trajectory:
+ if traj_point.timestamp > self.prediction_horizon:
+ break
+
+ for obstacle in state.obstacles:
+ # 获取障碍物中心(支持不同格式)
+ if hasattr(obstacle, "center_robot"):
+ obs_center = np.array(obstacle.center_robot)
+ elif hasattr(obstacle, "center"):
+ obs_center = np.array(obstacle.center)
+ else:
+ continue
+
+ # 计算距离
+ distance = np.linalg.norm(traj_point.position - obs_center)
+
+ # 考虑障碍物尺寸
+ if hasattr(obstacle, "dimensions"):
+ # 简化:使用最大维度作为半径
+ obs_radius = max(obstacle.dimensions) / 2
+ distance -= obs_radius
+
+ if distance < min_distance:
+ min_distance = distance
+ collision_obstacle = obstacle
+
+ # 判断是否碰撞
+ collision = min_distance < self.safety_margin
+
+ return ViolationResult(
+ violated=collision,
+ severity=self.severity,
+ confidence=0.9 if collision else 0.95,
+ description=(
+ f"检测到碰撞风险,最小距离 {min_distance:.2f}m"
+ if collision
+ else f"安全,最小距离 {min_distance:.2f}m"
+ ),
+ metrics={
+ "min_distance": float(min_distance),
+ "safety_margin": self.safety_margin,
+ },
+ correction_hint=(
+ {
+ "action": "avoid",
+ "obstacle_position": (
+ collision_obstacle.center_robot
+ if hasattr(collision_obstacle, "center_robot")
+ else None
+ ),
+ }
+ if collision and collision_obstacle
+ else None
+ ),
+ )
diff --git a/src/aylm/constitution/principles/ttc.py b/src/aylm/constitution/principles/ttc.py
new file mode 100644
index 0000000..8f871e7
--- /dev/null
+++ b/src/aylm/constitution/principles/ttc.py
@@ -0,0 +1,151 @@
+"""TTC(碰撞时间)安全原则。
+
+提供 TTC 计算的参考实现。
+"""
+
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from ..base import ConstitutionPrinciple, Severity, ViolationResult
+from ..registry import ConstitutionRegistry
+
+if TYPE_CHECKING:
+ from ..types import AIDecision, SceneState
+
+
+@ConstitutionRegistry.register_principle("ttc_safety")
+class TTCSafetyPrinciple(ConstitutionPrinciple):
+ """TTC 安全原则。
+
+ 计算与动态障碍物的碰撞时间(Time To Collision)。
+
+ 数学表述:
+ ∀o ∈ O_dynamic: TTC(ego, o) > τ_min
+
+ 其中:
+ TTC = (d - d_safe) / v_rel
+
+ 参数:
+ warning_threshold: 警告阈值(秒)
+ critical_threshold: 关键阈值(秒)
+ min_safe_distance: 最小安全距离(米)
+ """
+
+ def __init__(
+ self,
+ warning_threshold: float = 3.0,
+ critical_threshold: float = 1.5,
+ min_safe_distance: float = 2.0,
+ ):
+ self.warning_threshold = warning_threshold
+ self.critical_threshold = critical_threshold
+ self.min_safe_distance = min_safe_distance
+
+ @property
+ def name(self) -> str:
+ return "ttc_safety"
+
+ @property
+ def severity(self) -> Severity:
+ return Severity.HIGH
+
+ @property
+ def description(self) -> str:
+ return "检测与动态障碍物的碰撞时间是否安全"
+
+ def evaluate(
+ self,
+ state: "SceneState",
+ decision: "AIDecision",
+ ) -> ViolationResult:
+ """评估 TTC 安全性。
+
+ Args:
+ state: 场景状态
+ decision: AI 决策
+
+ Returns:
+ ViolationResult: 违规检测结果
+ """
+ if not state.obstacles:
+ return ViolationResult(
+ violated=False,
+ severity=self.severity,
+ confidence=1.0,
+ description="无障碍物",
+ )
+
+ min_ttc = float("inf")
+
+ ego_pos = state.ego_state.position
+ ego_vel = state.ego_state.velocity
+
+ for obstacle in state.obstacles:
+ # 只检测动态障碍物
+ if hasattr(obstacle, "motion") and obstacle.motion:
+ motion = obstacle.motion
+ if motion.is_stationary:
+ continue
+
+ # 获取障碍物位置和速度
+ if hasattr(obstacle, "center_robot"):
+ obs_pos = np.array(obstacle.center_robot)
+ else:
+ continue
+
+ obs_vel = np.array(motion.velocity_robot)
+
+ # 计算相对位置和速度
+ rel_pos = obs_pos - ego_pos
+ rel_vel = obs_vel - ego_vel
+
+ # 计算距离
+ distance = np.linalg.norm(rel_pos)
+
+ # 计算相对速度在连线方向的分量
+ if distance > 0:
+ direction = rel_pos / distance
+ closing_speed = -np.dot(rel_vel, direction)
+
+ # 只有在接近时才计算 TTC
+ if closing_speed > 0.1: # 接近速度阈值
+ ttc = (distance - self.min_safe_distance) / closing_speed
+
+ if ttc < min_ttc:
+ min_ttc = ttc
+ _ = obstacle # 保留用于未来扩展
+
+ # 判断违规
+ if min_ttc < self.critical_threshold:
+ violated = True
+ severity = Severity.CRITICAL
+ desc = f"TTC 关键警告: {min_ttc:.1f}s < {self.critical_threshold}s"
+ elif min_ttc < self.warning_threshold:
+ violated = True
+ severity = Severity.HIGH
+ desc = f"TTC 警告: {min_ttc:.1f}s < {self.warning_threshold}s"
+ else:
+ violated = False
+ severity = self.severity
+ desc = f"TTC 安全: {min_ttc:.1f}s"
+
+ return ViolationResult(
+ violated=violated,
+ severity=severity,
+ confidence=0.85,
+ description=desc,
+ metrics={
+ "min_ttc": float(min_ttc) if min_ttc != float("inf") else -1,
+ "warning_threshold": self.warning_threshold,
+ "critical_threshold": self.critical_threshold,
+ },
+ correction_hint=(
+ {
+ "action": "slow_down",
+ "target_ttc": self.warning_threshold,
+ }
+ if violated
+ else None
+ ),
+ )
diff --git a/src/aylm/constitution/registry.py b/src/aylm/constitution/registry.py
new file mode 100644
index 0000000..2c45f2f
--- /dev/null
+++ b/src/aylm/constitution/registry.py
@@ -0,0 +1,165 @@
+"""宪法原则注册表。
+
+提供插件机制,允许第三方注册自定义的宪法原则和打分器。
+"""
+
+from typing import TYPE_CHECKING, ClassVar
+
+if TYPE_CHECKING:
+ from .base import ConstitutionPrinciple
+ from .scorer import SafetyScorer
+ from .training import TrainingSignalGenerator
+
+
+class ConstitutionRegistry:
+ """宪法原则注册表。
+
+ 支持第三方插件注册自定义的宪法原则、打分器和训练信号生成器。
+
+ Example:
+ >>> from aylm.constitution import ConstitutionRegistry, ConstitutionPrinciple
+ >>>
+ >>> @ConstitutionRegistry.register_principle("my_custom_rule")
+ ... class MyCustomPrinciple(ConstitutionPrinciple):
+ ... @property
+ ... def name(self) -> str:
+ ... return "my_custom_rule"
+ ... # ... 实现其他方法
+ >>>
+ >>> # 获取已注册的原则
+ >>> principle_cls = ConstitutionRegistry.get_principle("my_custom_rule")
+ >>> principle = principle_cls()
+ """
+
+ _principles: ClassVar[dict[str, type["ConstitutionPrinciple"]]] = {}
+ _scorers: ClassVar[dict[str, type["SafetyScorer"]]] = {}
+ _generators: ClassVar[dict[str, type["TrainingSignalGenerator"]]] = {}
+
+ @classmethod
+ def register_principle(cls, name: str):
+ """装饰器:注册自定义宪法原则。
+
+ Args:
+ name: 原则名称(唯一标识符)
+
+ Example:
+ >>> @ConstitutionRegistry.register_principle("no_collision")
+ ... class NoCollisionPrinciple(ConstitutionPrinciple):
+ ... pass
+ """
+
+ def decorator(principle_cls: type["ConstitutionPrinciple"]):
+ cls._principles[name] = principle_cls
+ return principle_cls
+
+ return decorator
+
+ @classmethod
+ def register_scorer(cls, name: str):
+ """装饰器:注册自定义打分器。
+
+ Args:
+ name: 打分器名称
+
+ Example:
+ >>> @ConstitutionRegistry.register_scorer("weighted")
+ ... class WeightedScorer(SafetyScorer):
+ ... pass
+ """
+
+ def decorator(scorer_cls: type["SafetyScorer"]):
+ cls._scorers[name] = scorer_cls
+ return scorer_cls
+
+ return decorator
+
+ @classmethod
+ def register_generator(cls, name: str):
+ """装饰器:注册自定义训练信号生成器。
+
+ Args:
+ name: 生成器名称
+
+ Example:
+ >>> @ConstitutionRegistry.register_generator("tfrecord")
+ ... class TFRecordGenerator(TrainingSignalGenerator):
+ ... pass
+ """
+
+ def decorator(generator_cls: type["TrainingSignalGenerator"]):
+ cls._generators[name] = generator_cls
+ return generator_cls
+
+ return decorator
+
+ @classmethod
+ def get_principle(cls, name: str) -> type["ConstitutionPrinciple"] | None:
+ """获取已注册的原则类。"""
+ return cls._principles.get(name)
+
+ @classmethod
+ def get_scorer(cls, name: str) -> type["SafetyScorer"] | None:
+ """获取已注册的打分器类。"""
+ return cls._scorers.get(name)
+
+ @classmethod
+ def get_generator(cls, name: str) -> type["TrainingSignalGenerator"] | None:
+ """获取已注册的生成器类。"""
+ return cls._generators.get(name)
+
+ @classmethod
+ def list_principles(cls) -> list[str]:
+ """列出所有已注册的原则名称。"""
+ return list(cls._principles.keys())
+
+ @classmethod
+ def list_scorers(cls) -> list[str]:
+ """列出所有已注册的打分器名称。"""
+ return list(cls._scorers.keys())
+
+ @classmethod
+ def list_generators(cls) -> list[str]:
+ """列出所有已注册的生成器名称。"""
+ return list(cls._generators.keys())
+
+ @classmethod
+ def create_principle(cls, name: str, **kwargs) -> "ConstitutionPrinciple":
+ """创建原则实例。
+
+ Args:
+ name: 原则名称
+ **kwargs: 传递给原则构造函数的参数
+
+ Returns:
+ 原则实例
+
+ Raises:
+ KeyError: 如果原则未注册
+ """
+ principle_cls = cls._principles.get(name)
+ if principle_cls is None:
+ raise KeyError(f"原则 '{name}' 未注册。已注册: {cls.list_principles()}")
+ return principle_cls(**kwargs)
+
+ @classmethod
+ def create_scorer(cls, name: str, **kwargs) -> "SafetyScorer":
+ """创建打分器实例。"""
+ scorer_cls = cls._scorers.get(name)
+ if scorer_cls is None:
+ raise KeyError(f"打分器 '{name}' 未注册。已注册: {cls.list_scorers()}")
+ return scorer_cls(**kwargs)
+
+ @classmethod
+ def create_generator(cls, name: str, **kwargs) -> "TrainingSignalGenerator":
+ """创建生成器实例。"""
+ generator_cls = cls._generators.get(name)
+ if generator_cls is None:
+ raise KeyError(f"生成器 '{name}' 未注册。已注册: {cls.list_generators()}")
+ return generator_cls(**kwargs)
+
+ @classmethod
+ def clear(cls) -> None:
+ """清除所有注册(主要用于测试)。"""
+ cls._principles.clear()
+ cls._scorers.clear()
+ cls._generators.clear()
diff --git a/src/aylm/constitution/scorer.py b/src/aylm/constitution/scorer.py
new file mode 100644
index 0000000..453116e
--- /dev/null
+++ b/src/aylm/constitution/scorer.py
@@ -0,0 +1,137 @@
+"""安全打分器模块。
+
+本模块定义了安全打分的抽象接口:
+- SafetyScore: 安全评分结果
+- SafetyScorer: 安全打分器基类
+
+用户可以继承 SafetyScorer 实现自定义的打分逻辑。
+"""
+
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from enum import Enum
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+ from ..tools.obstacle_marker import ObstacleBox3D
+ from .types import AIDecision, EgoState
+
+
+class RecommendedAction(Enum):
+ """推荐动作。"""
+
+ SAFE = "safe" # 安全,无需干预
+ CAUTION = "caution" # 注意,轻微风险
+ WARNING = "warning" # 警告,需要关注
+ INTERVENTION = "intervention" # 干预,建议接管
+ EMERGENCY_STOP = "emergency_stop" # 紧急停车
+
+
+@dataclass
+class SafetyScore:
+ """安全评分结果。
+
+ Attributes:
+ overall: 综合安全分数 (0.0-1.0,1.0 最安全)
+ collision_score: 碰撞风险分数
+ ttc_score: TTC(碰撞时间)分数
+ boundary_score: 边界合规分数
+ violations: 违规的原则名称列表
+ violation_details: 违规详情
+ recommended_action: 推荐动作
+ confidence: 评分置信度
+ """
+
+ overall: float
+ collision_score: float = 1.0
+ ttc_score: float = 1.0
+ boundary_score: float = 1.0
+ violations: list[str] = field(default_factory=list)
+ violation_details: list[dict[str, Any]] = field(default_factory=list)
+ recommended_action: RecommendedAction = RecommendedAction.SAFE
+ confidence: float = 1.0
+
+ def to_dict(self) -> dict[str, Any]:
+ """转换为字典格式。"""
+ return {
+ "overall": self.overall,
+ "scores": {
+ "collision": self.collision_score,
+ "ttc": self.ttc_score,
+ "boundary": self.boundary_score,
+ },
+ "violations": self.violations,
+ "violation_details": self.violation_details,
+ "recommended_action": self.recommended_action.value,
+ "confidence": self.confidence,
+ }
+
+ @property
+ def is_safe(self) -> bool:
+ """是否安全(无违规)。"""
+ return len(self.violations) == 0
+
+ @property
+ def needs_intervention(self) -> bool:
+ """是否需要干预。"""
+ return self.recommended_action in (
+ RecommendedAction.INTERVENTION,
+ RecommendedAction.EMERGENCY_STOP,
+ )
+
+
+class SafetyScorer(ABC):
+ """安全打分器基类。
+
+ 用户可以继承此类实现自定义的打分逻辑。
+ 不同的 AI 系统可能有不同的安全标准和打分方式。
+
+ Example:
+ >>> class MySafetyScorer(SafetyScorer):
+ ... def score(self, obstacles, ego_state, ai_decision) -> SafetyScore:
+ ... # 实现自定义打分逻辑
+ ... collision_risk = self._check_collision(obstacles, ai_decision)
+ ... ttc = self._compute_ttc(obstacles, ego_state)
+ ... return SafetyScore(
+ ... overall=min(collision_risk, ttc),
+ ... collision_score=collision_risk,
+ ... ttc_score=ttc,
+ ... )
+ """
+
+ @abstractmethod
+ def score(
+ self,
+ obstacles: list["ObstacleBox3D"],
+ ego_state: "EgoState",
+ ai_decision: "AIDecision",
+ ) -> SafetyScore:
+ """计算安全分数。
+
+ Args:
+ obstacles: 3D 障碍物列表(来自 A-YLM 感知模块)
+ ego_state: 自车状态(位置、速度、航向等)
+ ai_decision: AI 的决策(规划轨迹、控制指令等)
+
+ Returns:
+ SafetyScore: 安全评分结果
+ """
+ pass
+
+ def score_from_scene(
+ self,
+ scene_data: dict[str, Any],
+ ) -> SafetyScore:
+ """从场景数据计算安全分数(便捷方法)。
+
+ Args:
+ scene_data: 场景数据字典,包含 obstacles、ego_state、ai_decision
+
+ Returns:
+ SafetyScore: 安全评分结果
+ """
+ return self.score(
+ obstacles=scene_data.get("obstacles", []),
+ ego_state=scene_data.get("ego_state"),
+ ai_decision=scene_data.get("ai_decision"),
+ )
diff --git a/src/aylm/constitution/training.py b/src/aylm/constitution/training.py
new file mode 100644
index 0000000..57b3508
--- /dev/null
+++ b/src/aylm/constitution/training.py
@@ -0,0 +1,175 @@
+"""训练信号生成模块。
+
+本模块定义了自监督学习的训练信号接口:
+- TrainingSignal: 训练信号数据结构
+- TrainingSignalGenerator: 训练信号生成器基类
+
+这是实现"几何宪法式 AI 自循环自训练"的关键模块。
+用户可以根据自己的 AI 训练框架实现具体的信号生成逻辑。
+"""
+
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from enum import Enum
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+ from .scorer import SafetyScore
+ from .types import AIDecision, SceneState
+
+
+class SignalType(Enum):
+ """训练信号类型。"""
+
+ POSITIVE = "positive" # 正样本:安全决策
+ NEGATIVE = "negative" # 负样本:违规决策
+ CORRECTION = "correction" # 纠正样本:包含正确行为指导
+
+
+@dataclass
+class TrainingSignal:
+ """训练信号。
+
+ 用于自监督学习的训练数据,可上传到云端用于 AI 模型改进。
+
+ Attributes:
+ timestamp: 时间戳
+ frame_id: 帧 ID
+ signal_type: 信号类型(正/负/纠正)
+ safety_score: 安全分数
+ violations: 违规的原则列表
+ scene_context: 场景上下文(障碍物、自车状态等)
+ ai_decision: AI 的原始决策
+ correction_target: 纠正目标(如果是纠正样本)
+ metadata: 额外元数据
+ """
+
+ timestamp: float
+ frame_id: int
+ signal_type: SignalType
+ safety_score: float
+ violations: list[str] = field(default_factory=list)
+ scene_context: dict[str, Any] = field(default_factory=dict)
+ ai_decision: dict[str, Any] = field(default_factory=dict)
+ correction_target: dict[str, Any] | None = None
+ metadata: dict[str, Any] = field(default_factory=dict)
+
+ def to_dict(self) -> dict[str, Any]:
+ """转换为字典格式(用于序列化)。"""
+ return {
+ "timestamp": self.timestamp,
+ "frame_id": self.frame_id,
+ "signal_type": self.signal_type.value,
+ "safety_score": self.safety_score,
+ "violations": self.violations,
+ "scene_context": self.scene_context,
+ "ai_decision": self.ai_decision,
+ "correction_target": self.correction_target,
+ "metadata": self.metadata,
+ }
+
+ @classmethod
+ def from_dict(cls, data: dict[str, Any]) -> "TrainingSignal":
+ """从字典创建实例。"""
+ return cls(
+ timestamp=data["timestamp"],
+ frame_id=data["frame_id"],
+ signal_type=SignalType(data["signal_type"]),
+ safety_score=data["safety_score"],
+ violations=data.get("violations", []),
+ scene_context=data.get("scene_context", {}),
+ ai_decision=data.get("ai_decision", {}),
+ correction_target=data.get("correction_target"),
+ metadata=data.get("metadata", {}),
+ )
+
+
+class TrainingSignalGenerator(ABC):
+ """训练信号生成器基类。
+
+ 用户可以继承此类实现自定义的训练信号生成逻辑。
+ 不同的 AI 训练框架可能需要不同格式的训练数据。
+
+ Example:
+ >>> class MySignalGenerator(TrainingSignalGenerator):
+ ... def generate(self, safety_result, scene_state, ai_decision):
+ ... if not safety_result.is_safe:
+ ... return TrainingSignal(
+ ... timestamp=scene_state.timestamp,
+ ... frame_id=scene_state.frame_id,
+ ... signal_type=SignalType.NEGATIVE,
+ ... safety_score=safety_result.overall,
+ ... violations=safety_result.violations,
+ ... )
+ ... return None
+ """
+
+ @abstractmethod
+ def generate(
+ self,
+ safety_result: "SafetyScore",
+ scene_state: "SceneState",
+ ai_decision: "AIDecision",
+ ) -> TrainingSignal | None:
+ """生成训练信号。
+
+ Args:
+ safety_result: 安全评分结果
+ scene_state: 当前场景状态
+ ai_decision: AI 的决策
+
+ Returns:
+ TrainingSignal: 训练信号,如果不需要生成则返回 None
+ """
+ pass
+
+ @abstractmethod
+ def export(
+ self,
+ signals: list[TrainingSignal],
+ output_path: str,
+ format: str = "json",
+ ) -> None:
+ """导出训练信号。
+
+ Args:
+ signals: 训练信号列表
+ output_path: 输出路径
+ format: 导出格式(json, tfrecord, parquet 等)
+ """
+ pass
+
+ def should_generate_positive(self) -> bool:
+ """是否生成正样本(可覆盖)。
+
+ 默认不生成正样本,因为正样本数量通常远大于负样本。
+ 用户可以根据需要覆盖此方法。
+ """
+ return False
+
+ def filter_signals(
+ self,
+ signals: list[TrainingSignal],
+ min_score: float = 0.0,
+ max_score: float = 1.0,
+ signal_types: list[SignalType] | None = None,
+ ) -> list[TrainingSignal]:
+ """过滤训练信号。
+
+ Args:
+ signals: 原始信号列表
+ min_score: 最小安全分数
+ max_score: 最大安全分数
+ signal_types: 允许的信号类型
+
+ Returns:
+ 过滤后的信号列表
+ """
+ filtered = []
+ for signal in signals:
+ if not (min_score <= signal.safety_score <= max_score):
+ continue
+ if signal_types and signal.signal_type not in signal_types:
+ continue
+ filtered.append(signal)
+ return filtered
diff --git a/src/aylm/constitution/types.py b/src/aylm/constitution/types.py
new file mode 100644
index 0000000..0b2b1bc
--- /dev/null
+++ b/src/aylm/constitution/types.py
@@ -0,0 +1,136 @@
+"""宪法式 AI 类型定义。
+
+定义场景状态、AI 决策等核心数据结构。
+"""
+
+from dataclasses import dataclass, field
+from typing import Any
+
+import numpy as np
+from numpy.typing import NDArray
+
+
+@dataclass
+class EgoState:
+ """自车/机器人状态。
+
+ Attributes:
+ position: 位置 [x, y, z](机器人坐标系)
+ velocity: 速度 [vx, vy, vz](m/s)
+ heading: 航向角(弧度)
+ speed: 标量速度(m/s)
+ acceleration: 加速度 [ax, ay, az](m/s²)
+ dimensions: 尺寸 [length, width, height](米)
+ """
+
+ position: NDArray[np.float32]
+ velocity: NDArray[np.float32]
+ heading: float
+ speed: float
+ acceleration: NDArray[np.float32] | None = None
+ dimensions: NDArray[np.float32] | None = None
+
+ def to_dict(self) -> dict[str, Any]:
+ """转换为字典格式。"""
+ return {
+ "position": self.position.tolist(),
+ "velocity": self.velocity.tolist(),
+ "heading": self.heading,
+ "speed": self.speed,
+ "acceleration": (
+ self.acceleration.tolist() if self.acceleration is not None else None
+ ),
+ "dimensions": (
+ self.dimensions.tolist() if self.dimensions is not None else None
+ ),
+ }
+
+
+@dataclass
+class TrajectoryPoint:
+ """轨迹点。"""
+
+ position: NDArray[np.float32] # [x, y, z]
+ velocity: NDArray[np.float32] | None = None # [vx, vy, vz]
+ timestamp: float = 0.0 # 相对时间(秒)
+
+
+@dataclass
+class AIDecision:
+ """AI 决策。
+
+ 表示端到端 AI 系统输出的决策,可以是轨迹规划或控制指令。
+
+ Attributes:
+ decision_type: 决策类型(trajectory, control, waypoint)
+ trajectory: 规划轨迹(轨迹点列表)
+ control: 控制指令(steering, throttle, brake)
+ target_speed: 目标速度(m/s)
+ confidence: 决策置信度
+ metadata: 额外元数据
+ """
+
+ decision_type: str # "trajectory", "control", "waypoint"
+ trajectory: list[TrajectoryPoint] = field(default_factory=list)
+ control: dict[str, float] = field(default_factory=dict) # steering, throttle, brake
+ target_speed: float | None = None
+ confidence: float = 1.0
+ metadata: dict[str, Any] = field(default_factory=dict)
+
+ def to_dict(self) -> dict[str, Any]:
+ """转换为字典格式。"""
+ return {
+ "decision_type": self.decision_type,
+ "trajectory": [
+ {
+ "position": p.position.tolist(),
+ "velocity": p.velocity.tolist() if p.velocity is not None else None,
+ "timestamp": p.timestamp,
+ }
+ for p in self.trajectory
+ ],
+ "control": self.control,
+ "target_speed": self.target_speed,
+ "confidence": self.confidence,
+ "metadata": self.metadata,
+ }
+
+
+@dataclass
+class SceneState:
+ """场景状态。
+
+ 包含当前帧的所有感知信息,供宪法原则评估使用。
+
+ Attributes:
+ frame_id: 帧 ID
+ timestamp: 时间戳(秒)
+ ego_state: 自车状态
+ obstacles: 障碍物列表(来自 A-YLM 感知模块)
+ lane_boundaries: 车道边界(可选)
+ traffic_signs: 交通标志(可选)
+ metadata: 额外元数据
+ """
+
+ frame_id: int
+ timestamp: float
+ ego_state: EgoState
+ obstacles: list[Any] = field(default_factory=list) # ObstacleBox3D 列表
+ lane_boundaries: list[Any] | None = None
+ traffic_signs: list[Any] | None = None
+ metadata: dict[str, Any] = field(default_factory=dict)
+
+ def to_dict(self) -> dict[str, Any]:
+ """转换为字典格式。"""
+ return {
+ "frame_id": self.frame_id,
+ "timestamp": self.timestamp,
+ "ego_state": self.ego_state.to_dict(),
+ "obstacles": [
+ obs.to_dict() if hasattr(obs, "to_dict") else obs
+ for obs in self.obstacles
+ ],
+ "lane_boundaries": self.lane_boundaries,
+ "traffic_signs": self.traffic_signs,
+ "metadata": self.metadata,
+ }
diff --git a/src/aylm/tools/__init__.py b/src/aylm/tools/__init__.py
index b73e5de..f46f532 100644
--- a/src/aylm/tools/__init__.py
+++ b/src/aylm/tools/__init__.py
@@ -6,7 +6,22 @@
transform_for_navigation,
transform_obstacle_center,
)
+from .motion_estimator import (
+ KalmanConfig,
+ MotionEstimator,
+ MotionVector,
+ TrackedObject3D,
+ create_tracked_object,
+)
+from .multiframe_fusion import (
+ FramePose,
+ FusionResult,
+ MultiframeFusion,
+ RegistrationConfig,
+ RegistrationResult,
+)
from .object_detector import DetectorConfig, ObjectDetector
+from .object_tracker import MultiObjectTracker, TrackedObject, TrackerConfig
from .obstacle_marker import (
ObstacleBox3D,
ObstacleMarker,
@@ -79,4 +94,20 @@
"opencv_to_robot",
"robot_to_opencv",
"transform_obstacle_center",
+ # 多帧融合
+ "MultiframeFusion",
+ "RegistrationConfig",
+ "RegistrationResult",
+ "FramePose",
+ "FusionResult",
+ # 运动估计
+ "MotionEstimator",
+ "MotionVector",
+ "TrackedObject3D",
+ "KalmanConfig",
+ "create_tracked_object",
+ # 目标跟踪
+ "MultiObjectTracker",
+ "TrackedObject",
+ "TrackerConfig",
]
diff --git a/src/aylm/tools/coordinate_utils.py b/src/aylm/tools/coordinate_utils.py
index 7c69813..295edb8 100644
--- a/src/aylm/tools/coordinate_utils.py
+++ b/src/aylm/tools/coordinate_utils.py
@@ -223,7 +223,7 @@ def _read_ply(
raise ValueError("PLY文件数据不完整")
values = line.split()
points.append([float(values[0]), float(values[1]), float(values[2])])
- if has_colors and len(values) >= 6:
+ if has_colors and colors is not None and len(values) >= 6:
colors.append([int(values[3]), int(values[4]), int(values[5])])
return header_lines, np.array(points), has_colors, colors
diff --git a/src/aylm/tools/motion_estimator.py b/src/aylm/tools/motion_estimator.py
new file mode 100644
index 0000000..e67046d
--- /dev/null
+++ b/src/aylm/tools/motion_estimator.py
@@ -0,0 +1,408 @@
+"""运动矢量估计器模块。
+
+基于连续帧的 3D 位置变化计算速度,使用 Kalman 滤波器平滑轨迹。
+"""
+
+from dataclasses import dataclass
+from typing import Optional
+
+import numpy as np
+from numpy.typing import NDArray
+
+from .coordinate_utils import opencv_to_robot
+from .semantic_types import SemanticLabel
+
+
+@dataclass
+class MotionVector:
+ """运动矢量。"""
+
+ velocity_cv: NDArray[np.float32] # OpenCV 坐标系速度 [vx, vy, vz] m/s
+ velocity_robot: NDArray[np.float32] # 机器人坐标系速度 [vx, vy, vz] m/s
+ speed: float # 标量速度 m/s
+ heading: float # 航向角(弧度,相对于机器人前进方向)
+ is_stationary: bool # 是否静止(速度 < 阈值)
+
+
+@dataclass
+class TrackedObject3D:
+ """3D 跟踪对象。"""
+
+ track_id: int
+ center_cv: NDArray[np.float32] # OpenCV 坐标系中心
+ center_robot: NDArray[np.float32] # 机器人坐标系中心
+ dimensions: NDArray[np.float32] # [width, height, depth]
+ motion: Optional[MotionVector] # 运动信息
+ semantic_label: SemanticLabel
+ confidence: float
+ frame_id: int
+ timestamp: float # 秒
+
+
+@dataclass
+class KalmanConfig:
+ """Kalman 滤波器配置。"""
+
+ process_noise: float = 0.1 # 过程噪声
+ measurement_noise: float = 0.5 # 观测噪声
+ initial_covariance: float = 1.0 # 初始协方差
+
+
+@dataclass
+class _TrackState:
+ """单个轨迹的 Kalman 滤波器状态。"""
+
+ # 状态向量: [x, y, z, vx, vy, vz]
+ state: NDArray[np.float64]
+ # 协方差矩阵 6x6
+ covariance: NDArray[np.float64]
+ # 上一帧 ID
+ last_frame_id: int
+ # 上一帧时间戳
+ last_timestamp: float
+ # 初始化标志
+ initialized: bool = False
+
+
+class MotionEstimator:
+ """运动矢量估计器。
+
+ 使用 Kalman 滤波器平滑轨迹并估计速度。
+
+ 状态向量: [x, y, z, vx, vy, vz]
+ 观测向量: [x, y, z]
+ """
+
+ def __init__(
+ self,
+ fps: float = 30.0,
+ stationary_threshold: float = 0.1,
+ kalman_config: Optional[KalmanConfig] = None,
+ ):
+ """初始化运动估计器。
+
+ Args:
+ fps: 帧率,用于计算默认时间间隔
+ stationary_threshold: 静止判定阈值(m/s)
+ kalman_config: Kalman 滤波器配置
+ """
+ self.fps = fps
+ self.dt_default = 1.0 / fps
+ self.stationary_threshold = stationary_threshold
+ self.config = kalman_config or KalmanConfig()
+
+ # 轨迹状态字典 {track_id: _TrackState}
+ self._tracks: dict[int, _TrackState] = {}
+
+ # 预计算 Kalman 矩阵
+ self._init_kalman_matrices()
+
+ def _init_kalman_matrices(self) -> None:
+ """初始化 Kalman 滤波器矩阵。"""
+ # 观测矩阵 H: 3x6,只观测位置
+ self._H = np.array(
+ [
+ [1, 0, 0, 0, 0, 0],
+ [0, 1, 0, 0, 0, 0],
+ [0, 0, 1, 0, 0, 0],
+ ],
+ dtype=np.float64,
+ )
+
+ # 观测噪声协方差 R: 3x3
+ self._R = np.eye(3, dtype=np.float64) * self.config.measurement_noise
+
+ def _get_transition_matrix(self, dt: float) -> NDArray[np.float64]:
+ """获取状态转移矩阵 F。
+
+ Args:
+ dt: 时间间隔(秒)
+
+ Returns:
+ 6x6 状态转移矩阵
+ """
+ return np.array(
+ [
+ [1, 0, 0, dt, 0, 0],
+ [0, 1, 0, 0, dt, 0],
+ [0, 0, 1, 0, 0, dt],
+ [0, 0, 0, 1, 0, 0],
+ [0, 0, 0, 0, 1, 0],
+ [0, 0, 0, 0, 0, 1],
+ ],
+ dtype=np.float64,
+ )
+
+ def _get_process_noise(self, dt: float) -> NDArray[np.float64]:
+ """获取过程噪声协方差矩阵 Q。
+
+ 使用离散白噪声加速度模型。
+
+ Args:
+ dt: 时间间隔(秒)
+
+ Returns:
+ 6x6 过程噪声协方差矩阵
+ """
+ q = self.config.process_noise
+ dt2 = dt * dt
+ dt3 = dt2 * dt
+ dt4 = dt3 * dt
+
+ # 离散白噪声加速度模型
+ return (
+ np.array(
+ [
+ [dt4 / 4, 0, 0, dt3 / 2, 0, 0],
+ [0, dt4 / 4, 0, 0, dt3 / 2, 0],
+ [0, 0, dt4 / 4, 0, 0, dt3 / 2],
+ [dt3 / 2, 0, 0, dt2, 0, 0],
+ [0, dt3 / 2, 0, 0, dt2, 0],
+ [0, 0, dt3 / 2, 0, 0, dt2],
+ ],
+ dtype=np.float64,
+ )
+ * q
+ )
+
+ def _init_track(
+ self, position: NDArray, frame_id: int, timestamp: float
+ ) -> _TrackState:
+ """初始化新轨迹。
+
+ Args:
+ position: 初始位置 [x, y, z]
+ frame_id: 帧 ID
+ timestamp: 时间戳
+
+ Returns:
+ 新的轨迹状态
+ """
+ state = np.zeros(6, dtype=np.float64)
+ state[:3] = position
+
+ covariance = np.eye(6, dtype=np.float64) * self.config.initial_covariance
+ # 速度初始不确定性更大
+ covariance[3:, 3:] *= 10.0
+
+ return _TrackState(
+ state=state,
+ covariance=covariance,
+ last_frame_id=frame_id,
+ last_timestamp=timestamp,
+ initialized=True,
+ )
+
+ def _predict(self, track: _TrackState, dt: float) -> None:
+ """Kalman 预测步骤。
+
+ Args:
+ track: 轨迹状态
+ dt: 时间间隔
+ """
+ F = self._get_transition_matrix(dt)
+ Q = self._get_process_noise(dt)
+
+ # 状态预测: x' = F * x
+ track.state = F @ track.state
+
+ # 协方差预测: P' = F * P * F^T + Q
+ track.covariance = F @ track.covariance @ F.T + Q
+
+ def _update_kalman(
+ self, track: _TrackState, measurement: NDArray[np.float64]
+ ) -> None:
+ """Kalman 更新步骤。
+
+ Args:
+ track: 轨迹状态
+ measurement: 观测值 [x, y, z]
+ """
+ H = self._H
+ R = self._R
+
+ # 创新(残差): y = z - H * x
+ y = measurement - H @ track.state
+
+ # 创新协方差: S = H * P * H^T + R
+ S = H @ track.covariance @ H.T + R
+
+ # Kalman 增益: K = P * H^T * S^(-1)
+ K = track.covariance @ H.T @ np.linalg.inv(S)
+
+ # 状态更新: x = x + K * y
+ track.state = track.state + K @ y
+
+ # 协方差更新: P = (I - K * H) * P
+ identity = np.eye(6, dtype=np.float64)
+ track.covariance = (identity - K @ H) @ track.covariance
+
+ def update(
+ self,
+ track_id: int,
+ position_cv: NDArray,
+ frame_id: int,
+ timestamp: Optional[float] = None,
+ ) -> Optional[MotionVector]:
+ """更新轨迹,返回运动矢量。
+
+ Args:
+ track_id: 轨迹 ID
+ position_cv: OpenCV 坐标系位置 [x, y, z]
+ frame_id: 帧 ID
+ timestamp: 时间戳(秒),如果为 None 则根据帧率计算
+
+ Returns:
+ 运动矢量,如果是第一帧则返回 None
+ """
+ position = np.asarray(position_cv, dtype=np.float64)
+
+ # 计算时间戳
+ if timestamp is None:
+ timestamp = frame_id / self.fps
+
+ # 检查是否是新轨迹
+ if track_id not in self._tracks:
+ self._tracks[track_id] = self._init_track(position, frame_id, timestamp)
+ return None
+
+ track = self._tracks[track_id]
+
+ # 计算时间间隔
+ dt = timestamp - track.last_timestamp
+ if dt <= 0:
+ dt = self.dt_default
+
+ # Kalman 预测
+ self._predict(track, dt)
+
+ # Kalman 更新
+ self._update_kalman(track, position)
+
+ # 更新时间信息
+ track.last_frame_id = frame_id
+ track.last_timestamp = timestamp
+
+ # 提取速度
+ velocity_cv = track.state[3:6].astype(np.float32)
+
+ # 转换到机器人坐标系
+ velocity_robot = opencv_to_robot(velocity_cv).astype(np.float32)
+
+ # 计算标量速度
+ speed = float(np.linalg.norm(velocity_cv))
+
+ # 计算航向角(在机器人坐标系中,相对于 X 轴/前进方向)
+ # heading = atan2(vy, vx),范围 [-pi, pi]
+ heading = float(np.arctan2(velocity_robot[1], velocity_robot[0]))
+
+ # 判断是否静止
+ is_stationary = speed < self.stationary_threshold
+
+ return MotionVector(
+ velocity_cv=velocity_cv,
+ velocity_robot=velocity_robot,
+ speed=speed,
+ heading=heading,
+ is_stationary=is_stationary,
+ )
+
+ def predict(self, track_id: int, dt: float) -> Optional[NDArray[np.float64]]:
+ """预测未来位置。
+
+ Args:
+ track_id: 轨迹 ID
+ dt: 预测时间间隔(秒)
+
+ Returns:
+ 预测的 OpenCV 坐标系位置 [x, y, z],如果轨迹不存在则返回 None
+ """
+ if track_id not in self._tracks:
+ return None
+
+ track = self._tracks[track_id]
+ F = self._get_transition_matrix(dt)
+
+ # 预测状态
+ predicted_state = F @ track.state
+
+ return predicted_state[:3]
+
+ def get_velocity(self, track_id: int) -> Optional[NDArray[np.float64]]:
+ """获取当前速度估计。
+
+ Args:
+ track_id: 轨迹 ID
+
+ Returns:
+ OpenCV 坐标系速度 [vx, vy, vz],如果轨迹不存在则返回 None
+ """
+ if track_id not in self._tracks:
+ return None
+
+ return self._tracks[track_id].state[3:6].copy()
+
+ def remove_track(self, track_id: int) -> bool:
+ """移除轨迹。
+
+ Args:
+ track_id: 轨迹 ID
+
+ Returns:
+ 是否成功移除
+ """
+ if track_id in self._tracks:
+ del self._tracks[track_id]
+ return True
+ return False
+
+ def clear(self) -> None:
+ """清除所有轨迹。"""
+ self._tracks.clear()
+
+ @property
+ def active_tracks(self) -> list[int]:
+ """获取活跃轨迹 ID 列表。"""
+ return list(self._tracks.keys())
+
+
+def create_tracked_object(
+ track_id: int,
+ center_cv: NDArray,
+ dimensions: NDArray,
+ semantic_label: SemanticLabel,
+ confidence: float,
+ frame_id: int,
+ timestamp: float,
+ motion: Optional[MotionVector] = None,
+) -> TrackedObject3D:
+ """创建 3D 跟踪对象。
+
+ Args:
+ track_id: 轨迹 ID
+ center_cv: OpenCV 坐标系中心
+ dimensions: 尺寸 [width, height, depth]
+ semantic_label: 语义标签
+ confidence: 置信度
+ frame_id: 帧 ID
+ timestamp: 时间戳
+ motion: 运动信息
+
+ Returns:
+ TrackedObject3D 实例
+ """
+ center_cv = np.asarray(center_cv, dtype=np.float32)
+ center_robot = opencv_to_robot(center_cv).astype(np.float32)
+ dimensions = np.asarray(dimensions, dtype=np.float32)
+
+ return TrackedObject3D(
+ track_id=track_id,
+ center_cv=center_cv,
+ center_robot=center_robot,
+ dimensions=dimensions,
+ motion=motion,
+ semantic_label=semantic_label,
+ confidence=confidence,
+ frame_id=frame_id,
+ timestamp=timestamp,
+ )
diff --git a/src/aylm/tools/multiframe_fusion.py b/src/aylm/tools/multiframe_fusion.py
new file mode 100644
index 0000000..002a741
--- /dev/null
+++ b/src/aylm/tools/multiframe_fusion.py
@@ -0,0 +1,597 @@
+"""多帧点云配准融合模块。
+
+使用 Open3D 实现 ICP 配准和位姿图优化,将多帧点云融合成全局地图。
+"""
+
+from __future__ import annotations
+
+import json
+import logging
+import time
+from dataclasses import dataclass, field
+from pathlib import Path
+from typing import Any
+
+import numpy as np
+from numpy.typing import NDArray
+
+from .pointcloud_voxelizer import PointCloud
+
+logger = logging.getLogger(__name__)
+
+# 检查 Open3D 是否可用
+try:
+ import open3d as o3d
+
+ HAS_OPEN3D = True
+except ImportError:
+ HAS_OPEN3D = False
+ o3d = None # type: ignore[assignment]
+
+
+@dataclass
+class RegistrationConfig:
+ """配准配置参数。"""
+
+ # ICP 配准参数
+ icp_max_correspondence_distance: float = 0.05 # ICP 最大对应点距离(米)
+ icp_max_iteration: int = 50 # ICP 最大迭代次数
+ icp_relative_fitness: float = 1e-6 # 相对适应度收敛阈值
+ icp_relative_rmse: float = 1e-6 # 相对 RMSE 收敛阈值
+
+ # 特征提取参数
+ voxel_size_for_features: float = 0.05 # 特征提取用的体素大小
+ fpfh_radius: float = 0.25 # FPFH 特征半径
+ fpfh_max_nn: int = 100 # FPFH 最大近邻数
+ normal_radius: float = 0.1 # 法向量估计半径
+
+ # 位姿图优化参数
+ pose_graph_edge_prune_threshold: float = 0.25 # 边修剪阈值
+ pose_graph_preference_loop_closure: float = 0.1 # 闭环偏好
+
+ # 全局融合参数
+ fusion_voxel_size: float = 0.02 # 融合后体素下采样大小
+
+ # 配准质量阈值
+ min_fitness: float = 0.3 # 最小配准适应度
+ min_points: int = 1000 # 最小点数要求
+
+
+@dataclass
+class FramePose:
+ """单帧位姿信息。"""
+
+ frame_index: int # 帧索引
+ transformation: NDArray[np.float64] # 4x4 变换矩阵
+ fitness: float = 0.0 # 配准适应度
+ rmse: float = 0.0 # 配准 RMSE
+ is_keyframe: bool = False # 是否为关键帧
+
+ def to_dict(self) -> dict[str, Any]:
+ """转换为字典。"""
+ return {
+ "index": self.frame_index,
+ "transformation": self.transformation.tolist(),
+ "fitness": self.fitness,
+ "rmse": self.rmse,
+ "is_keyframe": self.is_keyframe,
+ }
+
+ @classmethod
+ def from_dict(cls, data: dict[str, Any]) -> FramePose:
+ """从字典创建。"""
+ return cls(
+ frame_index=data["index"],
+ transformation=np.array(data["transformation"]),
+ fitness=data.get("fitness", 0.0),
+ rmse=data.get("rmse", 0.0),
+ is_keyframe=data.get("is_keyframe", False),
+ )
+
+
+@dataclass
+class RegistrationResult:
+ """配准结果。"""
+
+ transformation: NDArray[np.float64] # 4x4 变换矩阵
+ fitness: float # 适应度
+ inlier_rmse: float # 内点 RMSE
+ correspondence_count: int # 对应点数量
+
+
+@dataclass
+class FusionResult:
+ """融合结果。"""
+
+ fused_pointcloud: PointCloud # 融合后的点云
+ frame_poses: list[FramePose] = field(default_factory=list) # 所有帧的位姿
+ total_frames: int = 0 # 总帧数
+ successful_registrations: int = 0 # 成功配准数
+ fusion_time: float = 0.0 # 融合耗时
+
+
+class MultiframeFusion:
+ """多帧点云配准融合器。
+
+ 使用 ICP 算法进行相邻帧配准,位姿图优化进行全局一致性调整,
+ 最终将所有帧变换到世界坐标系并合并。
+
+ Example:
+ >>> config = RegistrationConfig(icp_max_correspondence_distance=0.05)
+ >>> fusion = MultiframeFusion(config)
+ >>> result = fusion.fuse_sequence(pointcloud_list)
+ >>> fusion.save_fused_map(result, "fused_map.ply")
+ """
+
+ def __init__(self, config: RegistrationConfig | None = None):
+ """初始化融合器。
+
+ Args:
+ config: 配准配置,为 None 时使用默认配置
+ """
+ if not HAS_OPEN3D:
+ raise ImportError("Open3D 未安装,请运行: pip install open3d")
+
+ self.config = config or RegistrationConfig()
+ logger.info("MultiframeFusion 初始化完成")
+
+ def fuse_sequence(
+ self,
+ pointclouds: list[PointCloud],
+ initial_poses: list[NDArray[np.float64]] | None = None,
+ ) -> FusionResult:
+ """融合点云序列。
+
+ Args:
+ pointclouds: 点云列表(按时间顺序)
+ initial_poses: 可选的初始位姿估计
+
+ Returns:
+ FusionResult: 融合结果
+ """
+ start_time = time.time()
+ n_frames = len(pointclouds)
+
+ if n_frames == 0:
+ logger.warning("输入点云列表为空")
+ return FusionResult(fused_pointcloud=PointCloud(np.zeros((0, 3))))
+
+ if n_frames == 1:
+ logger.info("只有一帧,直接返回")
+ pose = FramePose(0, np.eye(4), 1.0, 0.0, True)
+ return FusionResult(
+ fused_pointcloud=pointclouds[0],
+ frame_poses=[pose],
+ total_frames=1,
+ successful_registrations=1,
+ fusion_time=time.time() - start_time,
+ )
+
+ logger.info(f"开始融合 {n_frames} 帧点云")
+
+ # 1. 预处理所有点云
+ logger.info("预处理点云...")
+ processed = []
+ valid_indices = []
+ for i, pc in enumerate(pointclouds):
+ if len(pc.points) < self.config.min_points:
+ logger.warning(f"帧 {i} 点数不足 ({len(pc.points)}), 跳过")
+ continue
+ pcd, fpfh = self._preprocess_pointcloud(pc)
+ processed.append((pcd, fpfh, pc))
+ valid_indices.append(i)
+
+ if len(processed) < 2:
+ logger.error("有效帧数不足,无法配准")
+ return FusionResult(fused_pointcloud=PointCloud(np.zeros((0, 3))))
+
+ # 2. 相邻帧配准(前一帧 -> 后一帧)
+ logger.info("执行相邻帧配准...")
+ pairwise_results: list[RegistrationResult | None] = []
+ for i in range(len(processed) - 1):
+ source_pcd, source_fpfh, _ = processed[i]
+ target_pcd, target_fpfh, _ = processed[i + 1]
+
+ result = self._register_pair_o3d(
+ source_pcd, target_pcd, source_fpfh, target_fpfh
+ )
+
+ if result.fitness < self.config.min_fitness:
+ logger.warning(
+ f"帧 {valid_indices[i]} -> {valid_indices[i+1]} "
+ f"配准质量低 (fitness={result.fitness:.3f})"
+ )
+ pairwise_results.append(result)
+ logger.debug(
+ f"帧 {valid_indices[i]} -> {valid_indices[i+1]}: "
+ f"fitness={result.fitness:.3f}, rmse={result.inlier_rmse:.4f}"
+ )
+
+ # 3. 构建位姿图
+ logger.info("构建位姿图...")
+ pose_graph = self._build_pose_graph(processed, pairwise_results)
+
+ # 4. 优化位姿图
+ logger.info("优化位姿图...")
+ pose_graph = self._optimize_pose_graph(pose_graph)
+
+ # 5. 提取优化后的位姿
+ frame_poses = []
+ successful = 0
+ for i, node in enumerate(pose_graph.nodes):
+ original_idx = valid_indices[i]
+ prev_result = pairwise_results[i - 1] if i > 0 else None
+ pose = FramePose(
+ frame_index=original_idx,
+ transformation=np.asarray(node.pose),
+ fitness=prev_result.fitness if prev_result else 1.0,
+ rmse=prev_result.inlier_rmse if prev_result else 0.0,
+ is_keyframe=(i == 0),
+ )
+ frame_poses.append(pose)
+ if i == 0 or (
+ prev_result and prev_result.fitness >= self.config.min_fitness
+ ):
+ successful += 1
+
+ # 6. 变换并合并点云
+ logger.info("合并点云...")
+ original_pcs = [p[2] for p in processed]
+ fused_pc = self._transform_and_merge(original_pcs, frame_poses)
+
+ # 7. 体素下采样
+ logger.info("体素下采样...")
+ fused_pc = self._voxel_downsample(fused_pc, self.config.fusion_voxel_size)
+
+ fusion_time = time.time() - start_time
+ logger.info(
+ f"融合完成: {len(fused_pc.points)} 点, "
+ f"{successful}/{len(processed)} 帧成功, 耗时 {fusion_time:.2f}s"
+ )
+
+ return FusionResult(
+ fused_pointcloud=fused_pc,
+ frame_poses=frame_poses,
+ total_frames=n_frames,
+ successful_registrations=successful,
+ fusion_time=fusion_time,
+ )
+
+ def fuse_from_directory(
+ self,
+ input_dir: Path,
+ pattern: str = "vox_*.ply",
+ ) -> FusionResult:
+ """从目录加载并融合点云序列。
+
+ Args:
+ input_dir: 输入目录
+ pattern: 文件匹配模式
+
+ Returns:
+ FusionResult: 融合结果
+ """
+ input_dir = Path(input_dir)
+ ply_files = sorted(input_dir.glob(pattern))
+
+ if not ply_files:
+ logger.error(f"未找到匹配 {pattern} 的文件: {input_dir}")
+ return FusionResult(fused_pointcloud=PointCloud(np.zeros((0, 3))))
+
+ logger.info(f"加载 {len(ply_files)} 个点云文件")
+ pointclouds = []
+ for ply_path in ply_files:
+ pc = PointCloud.from_ply(ply_path)
+ pointclouds.append(pc)
+ logger.debug(f"加载 {ply_path.name}: {len(pc.points)} 点")
+
+ return self.fuse_sequence(pointclouds)
+
+ def register_pair(
+ self,
+ source: PointCloud,
+ target: PointCloud,
+ initial_transform: NDArray[np.float64] | None = None,
+ ) -> RegistrationResult:
+ """配准两帧点云。
+
+ Args:
+ source: 源点云
+ target: 目标点云
+ initial_transform: 初始变换估计
+
+ Returns:
+ RegistrationResult: 配准结果
+ """
+ source_pcd, source_fpfh = self._preprocess_pointcloud(source)
+ target_pcd, target_fpfh = self._preprocess_pointcloud(target)
+
+ if initial_transform is not None:
+ # 直接使用 ICP 精配准
+ return self._fine_registration(source_pcd, target_pcd, initial_transform)
+ else:
+ # 粗配准 + 精配准
+ return self._register_pair_o3d(
+ source_pcd, target_pcd, source_fpfh, target_fpfh
+ )
+
+ def save_fused_map(
+ self,
+ result: FusionResult,
+ output_path: Path,
+ include_poses: bool = True,
+ ) -> None:
+ """保存融合后的地图。
+
+ Args:
+ result: 融合结果
+ output_path: 输出 PLY 文件路径
+ include_poses: 是否同时保存位姿文件
+ """
+ output_path = Path(output_path)
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+
+ # 保存点云
+ result.fused_pointcloud.to_ply(output_path)
+ logger.info(f"融合地图已保存: {output_path}")
+
+ # 保存位姿
+ if include_poses and result.frame_poses:
+ poses_path = output_path.with_suffix(".poses.json")
+ self.save_poses(result.frame_poses, poses_path)
+
+ def save_poses(
+ self,
+ poses: list[FramePose],
+ output_path: Path,
+ ) -> None:
+ """保存位姿轨迹到 JSON 文件。
+
+ Args:
+ poses: 位姿列表
+ output_path: 输出路径
+ """
+ output_path = Path(output_path)
+
+ data = {
+ "version": "1.0",
+ "coordinate_system": "opencv",
+ "frames": [p.to_dict() for p in poses],
+ "statistics": {
+ "total_frames": len(poses),
+ "average_fitness": np.mean([p.fitness for p in poses]),
+ "average_rmse": np.mean([p.rmse for p in poses]),
+ },
+ }
+
+ with open(output_path, "w", encoding="utf-8") as f:
+ json.dump(data, f, indent=2, ensure_ascii=False)
+
+ logger.info(f"位姿轨迹已保存: {output_path}")
+
+ # ========== 内部方法 ==========
+
+ def _to_o3d(self, pc: PointCloud) -> o3d.geometry.PointCloud:
+ """转换为 Open3D 点云格式。"""
+ pcd = o3d.geometry.PointCloud()
+ pcd.points = o3d.utility.Vector3dVector(pc.points.astype(np.float64))
+ if pc.colors is not None:
+ colors = pc.colors.astype(np.float64)
+ if colors.max() > 1.0:
+ colors = colors / 255.0
+ pcd.colors = o3d.utility.Vector3dVector(colors)
+ return pcd
+
+ def _from_o3d(self, pcd: o3d.geometry.PointCloud) -> PointCloud:
+ """从 Open3D 点云格式转换。"""
+ points = np.asarray(pcd.points)
+ colors = None
+ if pcd.has_colors():
+ colors = (np.asarray(pcd.colors) * 255).astype(np.uint8)
+ return PointCloud(points=points, colors=colors)
+
+ def _preprocess_pointcloud(
+ self,
+ pc: PointCloud,
+ ) -> tuple[o3d.geometry.PointCloud, o3d.pipelines.registration.Feature]:
+ """预处理点云:下采样 + 计算法向量 + 提取 FPFH 特征。"""
+ pcd = self._to_o3d(pc)
+
+ # 体素下采样
+ pcd_down = pcd.voxel_down_sample(self.config.voxel_size_for_features)
+
+ # 估计法向量
+ pcd_down.estimate_normals(
+ o3d.geometry.KDTreeSearchParamHybrid(
+ radius=self.config.normal_radius, max_nn=30
+ )
+ )
+
+ # 计算 FPFH 特征
+ fpfh = o3d.pipelines.registration.compute_fpfh_feature(
+ pcd_down,
+ o3d.geometry.KDTreeSearchParamHybrid(
+ radius=self.config.fpfh_radius, max_nn=self.config.fpfh_max_nn
+ ),
+ )
+
+ return pcd_down, fpfh
+
+ def _register_pair_o3d(
+ self,
+ source_pcd: o3d.geometry.PointCloud,
+ target_pcd: o3d.geometry.PointCloud,
+ source_fpfh: o3d.pipelines.registration.Feature,
+ target_fpfh: o3d.pipelines.registration.Feature,
+ ) -> RegistrationResult:
+ """配准两帧点云(粗配准 + 精配准)。"""
+ # 粗配准: RANSAC
+ coarse_result = o3d.pipelines.registration.registration_ransac_based_on_feature_matching(
+ source_pcd,
+ target_pcd,
+ source_fpfh,
+ target_fpfh,
+ mutual_filter=True,
+ max_correspondence_distance=self.config.icp_max_correspondence_distance * 2,
+ estimation_method=o3d.pipelines.registration.TransformationEstimationPointToPoint(
+ False
+ ),
+ ransac_n=4,
+ checkers=[
+ o3d.pipelines.registration.CorrespondenceCheckerBasedOnEdgeLength(0.9),
+ o3d.pipelines.registration.CorrespondenceCheckerBasedOnDistance(
+ self.config.icp_max_correspondence_distance * 2
+ ),
+ ],
+ criteria=o3d.pipelines.registration.RANSACConvergenceCriteria(
+ max_iteration=4000000, confidence=0.999
+ ),
+ )
+
+ # 精配准: Point-to-Plane ICP
+ return self._fine_registration(
+ source_pcd, target_pcd, coarse_result.transformation
+ )
+
+ def _fine_registration(
+ self,
+ source_pcd: o3d.geometry.PointCloud,
+ target_pcd: o3d.geometry.PointCloud,
+ initial_transform: NDArray[np.float64],
+ ) -> RegistrationResult:
+ """精配准:Point-to-Plane ICP。"""
+ # 确保有法向量
+ if not target_pcd.has_normals():
+ target_pcd.estimate_normals(
+ o3d.geometry.KDTreeSearchParamHybrid(
+ radius=self.config.normal_radius, max_nn=30
+ )
+ )
+
+ result = o3d.pipelines.registration.registration_icp(
+ source_pcd,
+ target_pcd,
+ self.config.icp_max_correspondence_distance,
+ initial_transform,
+ o3d.pipelines.registration.TransformationEstimationPointToPlane(),
+ o3d.pipelines.registration.ICPConvergenceCriteria(
+ relative_fitness=self.config.icp_relative_fitness,
+ relative_rmse=self.config.icp_relative_rmse,
+ max_iteration=self.config.icp_max_iteration,
+ ),
+ )
+
+ return RegistrationResult(
+ transformation=np.asarray(result.transformation),
+ fitness=result.fitness,
+ inlier_rmse=result.inlier_rmse,
+ correspondence_count=len(result.correspondence_set),
+ )
+
+ def _build_pose_graph(
+ self,
+ processed: list[tuple],
+ pairwise_results: list[RegistrationResult | None],
+ ) -> o3d.pipelines.registration.PoseGraph:
+ """构建位姿图。
+
+ 以第一帧为世界坐标系原点,累积后续帧的位姿。
+ 配准结果 T_{i->i+1} 表示把帧i变换到帧i+1。
+ 我们需要的是把每帧变换到帧0(世界坐标系)。
+ """
+ pose_graph = o3d.pipelines.registration.PoseGraph()
+ n_frames = len(processed)
+
+ # 第一帧位姿为单位矩阵(世界坐标系原点)
+ pose_graph.nodes.append(o3d.pipelines.registration.PoseGraphNode(np.eye(4)))
+
+ # 累积位姿:T_0^i = T_0^{i-1} @ inv(T_{i-1->i})
+ # 因为 T_{i-1->i} 把帧i-1变换到帧i,所以逆变换把帧i变换到帧i-1
+ cumulative_pose = np.eye(4)
+ for i in range(1, n_frames):
+ result = pairwise_results[i - 1]
+ if result is not None:
+ # T_{i-1->i} 的逆,把帧i变换到帧i-1的坐标系
+ inv_transform = np.linalg.inv(result.transformation)
+ cumulative_pose = cumulative_pose @ inv_transform
+ pose_graph.nodes.append(
+ o3d.pipelines.registration.PoseGraphNode(cumulative_pose.copy())
+ )
+
+ # 添加边(相邻帧)
+ for i in range(n_frames - 1):
+ result = pairwise_results[i]
+ if result is None:
+ continue
+
+ # 信息矩阵(基于配准质量)
+ information = (
+ np.eye(6) * result.fitness if result.fitness > 0 else np.eye(6) * 0.1
+ )
+
+ pose_graph.edges.append(
+ o3d.pipelines.registration.PoseGraphEdge(
+ i,
+ i + 1,
+ result.transformation,
+ information,
+ uncertain=False,
+ )
+ )
+
+ return pose_graph
+
+ def _optimize_pose_graph(
+ self,
+ pose_graph: o3d.pipelines.registration.PoseGraph,
+ ) -> o3d.pipelines.registration.PoseGraph:
+ """优化位姿图。"""
+ option = o3d.pipelines.registration.GlobalOptimizationOption(
+ max_correspondence_distance=self.config.icp_max_correspondence_distance,
+ edge_prune_threshold=self.config.pose_graph_edge_prune_threshold,
+ preference_loop_closure=self.config.pose_graph_preference_loop_closure,
+ reference_node=0,
+ )
+
+ o3d.pipelines.registration.global_optimization(
+ pose_graph,
+ o3d.pipelines.registration.GlobalOptimizationLevenbergMarquardt(),
+ o3d.pipelines.registration.GlobalOptimizationConvergenceCriteria(),
+ option,
+ )
+
+ return pose_graph
+
+ def _transform_and_merge(
+ self,
+ pointclouds: list[PointCloud],
+ poses: list[FramePose],
+ ) -> PointCloud:
+ """将所有点云变换到世界坐标系并合并。"""
+ all_points = []
+ all_colors = []
+ has_colors = pointclouds[0].colors is not None
+
+ for pc, pose in zip(pointclouds, poses):
+ # 应用变换
+ points_h = np.hstack([pc.points, np.ones((len(pc.points), 1))])
+ transformed = (pose.transformation @ points_h.T).T[:, :3]
+ all_points.append(transformed)
+
+ if has_colors and pc.colors is not None:
+ all_colors.append(pc.colors)
+
+ merged_points = np.vstack(all_points)
+ merged_colors = np.vstack(all_colors) if has_colors else None
+
+ return PointCloud(points=merged_points, colors=merged_colors)
+
+ def _voxel_downsample(
+ self,
+ pc: PointCloud,
+ voxel_size: float,
+ ) -> PointCloud:
+ """体素下采样去重。"""
+ pcd = self._to_o3d(pc)
+ pcd_down = pcd.voxel_down_sample(voxel_size)
+ return self._from_o3d(pcd_down)
diff --git a/src/aylm/tools/object_detector.py b/src/aylm/tools/object_detector.py
index 086be78..72a94fa 100644
--- a/src/aylm/tools/object_detector.py
+++ b/src/aylm/tools/object_detector.py
@@ -7,11 +7,14 @@
import logging
from dataclasses import dataclass, field
from pathlib import Path
-from typing import Optional, Union
+from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union
import numpy as np
from numpy.typing import NDArray
+if TYPE_CHECKING:
+ from ultralytics import YOLO
+
from .semantic_types import COCO_TO_SEMANTIC, Detection2D, SemanticLabel
logger = logging.getLogger(__name__)
@@ -22,7 +25,7 @@ class DetectorConfig:
"""检测器配置参数。"""
model_name: str = "yolo11n-seg.pt" # YOLO11 模型名称
- confidence_threshold: float = 0.5 # 置信度阈值
+ confidence_threshold: float = 0.25 # 置信度阈值(降低以检测更多目标)
iou_threshold: float = 0.45 # NMS IoU 阈值
device: str = "auto" # 设备:auto/cuda/mps/cpu
half_precision: bool = True # 是否使用半精度(FP16)
@@ -41,7 +44,7 @@ class ObjectDetector:
"""
# 默认检测类别:人、自行车、汽车、摩托车、公交车、卡车
- DEFAULT_CLASSES: list[int] = [0, 1, 2, 3, 5, 7]
+ DEFAULT_CLASSES: ClassVar[list[int]] = [0, 1, 2, 3, 5, 7]
def __init__(self, config: Optional[DetectorConfig] = None):
"""初始化检测器。
@@ -50,7 +53,7 @@ def __init__(self, config: Optional[DetectorConfig] = None):
config: 检测器配置,为 None 时使用默认配置
"""
self.config = config or DetectorConfig()
- self._model = None
+ self._model: Optional[YOLO] = None
self._device = self._detect_device()
# 如果未指定类别,使用默认类别
@@ -142,7 +145,7 @@ def unload(self) -> None:
def detect(
self,
- image: NDArray[np.uint8],
+ image: NDArray[Any],
return_masks: bool = True,
) -> list[Detection2D]:
"""执行目标检测。
@@ -263,13 +266,13 @@ def __enter__(self) -> "ObjectDetector":
self.load()
return self
- def __exit__(self, exc_type, exc_val, exc_tb) -> None:
+ def __exit__(self, _exc_type, _exc_val, _exc_tb) -> None:
"""上下文管理器出口,自动卸载模型。"""
self.unload()
def save_detection_image(
self,
- image: NDArray[np.uint8],
+ image: NDArray[Any],
detections: list[Detection2D],
output_path: Union[str, Path],
draw_masks: bool = True,
@@ -318,7 +321,7 @@ def save_detection_image(
# 绘制标签背景
label_text = f"{label_name} {det.confidence:.2f}"
- (text_w, text_h), baseline = cv2.getTextSize(
+ (text_w, text_h), _baseline = cv2.getTextSize(
label_text, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2
)
cv2.rectangle(
@@ -347,7 +350,9 @@ def save_detection_image(
)
mask_colored = np.zeros_like(result_image)
mask_colored[mask_resized > 0] = color
- result_image = cv2.addWeighted(result_image, 1.0, mask_colored, 0.3, 0)
+ result_image = cv2.addWeighted(
+ result_image, 1.0, mask_colored, 0.3, 0
+ ) # type: ignore[assignment]
# 保存图片
output_path.parent.mkdir(parents=True, exist_ok=True)
diff --git a/src/aylm/tools/object_tracker.py b/src/aylm/tools/object_tracker.py
new file mode 100644
index 0000000..63b111c
--- /dev/null
+++ b/src/aylm/tools/object_tracker.py
@@ -0,0 +1,357 @@
+"""多目标跟踪模块。
+
+基于 IoU 匹配的多目标跟踪器,使用匈牙利算法进行检测-轨迹匹配。
+支持轨迹的创建、更新和删除。
+"""
+
+import logging
+from dataclasses import dataclass, field
+from typing import Optional
+
+import numpy as np
+from numpy.typing import NDArray
+from scipy.optimize import linear_sum_assignment
+
+from .semantic_types import Detection2D
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class TrackedObject:
+ """跟踪目标。"""
+
+ track_id: int # 唯一跟踪 ID
+ bbox: NDArray[np.float32] # [x1, y1, x2, y2]
+ class_id: int # COCO 类别 ID
+ confidence: float # 置信度
+ age: int = 1 # 跟踪帧数
+ hits: int = 1 # 成功匹配次数
+ time_since_update: int = 0 # 距上次更新的帧数
+
+ def __post_init__(self) -> None:
+ """确保 bbox 是正确的类型。"""
+ if not isinstance(self.bbox, np.ndarray):
+ self.bbox = np.array(self.bbox, dtype=np.float32)
+ elif self.bbox.dtype != np.float32:
+ self.bbox = self.bbox.astype(np.float32)
+
+
+@dataclass
+class TrackerConfig:
+ """跟踪器配置参数。"""
+
+ max_age: int = 30 # 轨迹最大存活帧数(无匹配时)
+ min_hits: int = 3 # 轨迹确认所需最小匹配次数
+ iou_threshold: float = 0.3 # IoU 匹配阈值
+
+
+@dataclass
+class _Track:
+ """内部轨迹状态。"""
+
+ track_id: int
+ bbox: NDArray[np.float32]
+ class_id: int
+ confidence: float
+ age: int = 1
+ hits: int = 1
+ time_since_update: int = 0
+ velocity: NDArray[np.float32] = field(
+ default_factory=lambda: np.zeros(4, dtype=np.float32)
+ )
+
+ def predict(self) -> NDArray[np.float32]:
+ """预测下一帧位置(简单线性预测)。"""
+ predicted_bbox = self.bbox + self.velocity
+ # 确保边界框有效
+ predicted_bbox[2] = max(predicted_bbox[2], predicted_bbox[0] + 1)
+ predicted_bbox[3] = max(predicted_bbox[3], predicted_bbox[1] + 1)
+ return predicted_bbox
+
+ def update(self, detection: Detection2D) -> None:
+ """使用新检测更新轨迹。"""
+ new_bbox = detection.bbox.astype(np.float32)
+ # 更新速度(指数移动平均)
+ self.velocity = 0.7 * self.velocity + 0.3 * (new_bbox - self.bbox)
+ self.bbox = new_bbox
+ self.class_id = detection.class_id
+ self.confidence = detection.confidence
+ self.hits += 1
+ self.time_since_update = 0
+
+ def mark_missed(self) -> None:
+ """标记为未匹配。"""
+ self.time_since_update += 1
+ # 使用预测位置
+ self.bbox = self.predict()
+
+ def to_tracked_object(self) -> TrackedObject:
+ """转换为 TrackedObject。"""
+ return TrackedObject(
+ track_id=self.track_id,
+ bbox=self.bbox.copy(),
+ class_id=self.class_id,
+ confidence=self.confidence,
+ age=self.age,
+ hits=self.hits,
+ time_since_update=self.time_since_update,
+ )
+
+
+def _compute_iou(bbox1: NDArray[np.float32], bbox2: NDArray[np.float32]) -> float:
+ """计算两个边界框的 IoU。
+
+ Args:
+ bbox1: 边界框 1,[x1, y1, x2, y2]
+ bbox2: 边界框 2,[x1, y1, x2, y2]
+
+ Returns:
+ IoU 值
+ """
+ # 计算交集
+ x1 = max(bbox1[0], bbox2[0])
+ y1 = max(bbox1[1], bbox2[1])
+ x2 = min(bbox1[2], bbox2[2])
+ y2 = min(bbox1[3], bbox2[3])
+
+ if x2 <= x1 or y2 <= y1:
+ return 0.0
+
+ intersection = (x2 - x1) * (y2 - y1)
+
+ # 计算并集
+ area1 = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
+ area2 = (bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1])
+ union = area1 + area2 - intersection
+
+ if union <= 0:
+ return 0.0
+
+ return float(intersection / union)
+
+
+def _compute_iou_matrix(
+ tracks: list[_Track], detections: list[Detection2D]
+) -> NDArray[np.float32]:
+ """计算轨迹和检测之间的 IoU 矩阵。
+
+ Args:
+ tracks: 轨迹列表
+ detections: 检测列表
+
+ Returns:
+ IoU 矩阵,shape (len(tracks), len(detections))
+ """
+ if not tracks or not detections:
+ return np.empty((len(tracks), len(detections)), dtype=np.float32)
+
+ iou_matrix = np.zeros((len(tracks), len(detections)), dtype=np.float32)
+
+ for i, track in enumerate(tracks):
+ for j, det in enumerate(detections):
+ iou_matrix[i, j] = _compute_iou(track.bbox, det.bbox)
+
+ return iou_matrix
+
+
+class MultiObjectTracker:
+ """多目标跟踪器。
+
+ 基于 IoU 匹配的简化版跟踪器,使用匈牙利算法进行检测-轨迹匹配。
+
+ 使用示例:
+ >>> tracker = MultiObjectTracker()
+ >>> for frame_id, detections in enumerate(all_detections):
+ ... tracked = tracker.update(detections, frame_id)
+ ... for obj in tracked:
+ ... print(f"Track {obj.track_id}: {obj.bbox}")
+ """
+
+ def __init__(
+ self,
+ max_age: int = 30,
+ min_hits: int = 3,
+ iou_threshold: float = 0.3,
+ config: Optional[TrackerConfig] = None,
+ ):
+ """初始化跟踪器。
+
+ Args:
+ max_age: 轨迹最大存活帧数(无匹配时)
+ min_hits: 轨迹确认所需最小匹配次数
+ iou_threshold: IoU 匹配阈值
+ config: 跟踪器配置(优先级高于单独参数)
+ """
+ if config is not None:
+ self.config = config
+ else:
+ self.config = TrackerConfig(
+ max_age=max_age,
+ min_hits=min_hits,
+ iou_threshold=iou_threshold,
+ )
+
+ self._tracks: list[_Track] = []
+ self._next_id: int = 1
+ self._frame_count: int = 0
+
+ logger.info(
+ f"MultiObjectTracker 初始化: max_age={self.config.max_age}, "
+ f"min_hits={self.config.min_hits}, iou_threshold={self.config.iou_threshold}"
+ )
+
+ def update(
+ self, detections: list[Detection2D], frame_id: Optional[int] = None
+ ) -> list[TrackedObject]:
+ """更新跟踪器,返回跟踪结果。
+
+ Args:
+ detections: 当前帧的检测结果
+ frame_id: 帧 ID(可选,用于日志)
+
+ Returns:
+ 确认的跟踪目标列表
+ """
+ self._frame_count += 1
+ current_frame = frame_id if frame_id is not None else self._frame_count
+
+ logger.debug(f"帧 {current_frame}: 收到 {len(detections)} 个检测")
+
+ # 增加所有轨迹的年龄
+ for track in self._tracks:
+ track.age += 1
+
+ if not detections:
+ # 没有检测,标记所有轨迹为未匹配
+ for track in self._tracks:
+ track.mark_missed()
+ self._remove_dead_tracks()
+ return self._get_confirmed_tracks()
+
+ if not self._tracks:
+ # 没有轨迹,为所有检测创建新轨迹
+ for det in detections:
+ self._create_track(det)
+ return self._get_confirmed_tracks()
+
+ # 计算 IoU 矩阵
+ iou_matrix = _compute_iou_matrix(self._tracks, detections)
+
+ # 使用匈牙利算法进行匹配
+ matched_tracks, matched_dets, unmatched_tracks, unmatched_dets = (
+ self._hungarian_match(iou_matrix)
+ )
+
+ logger.debug(
+ f"匹配结果: {len(matched_tracks)} 匹配, "
+ f"{len(unmatched_tracks)} 未匹配轨迹, "
+ f"{len(unmatched_dets)} 未匹配检测"
+ )
+
+ # 更新匹配的轨迹
+ for track_idx, det_idx in zip(matched_tracks, matched_dets):
+ self._tracks[track_idx].update(detections[det_idx])
+
+ # 标记未匹配的轨迹
+ for track_idx in unmatched_tracks:
+ self._tracks[track_idx].mark_missed()
+
+ # 为未匹配的检测创建新轨迹
+ for det_idx in unmatched_dets:
+ self._create_track(detections[det_idx])
+
+ # 删除死亡轨迹
+ self._remove_dead_tracks()
+
+ return self._get_confirmed_tracks()
+
+ def _hungarian_match(
+ self, iou_matrix: NDArray[np.float32]
+ ) -> tuple[list[int], list[int], list[int], list[int]]:
+ """使用匈牙利算法进行匹配。
+
+ Args:
+ iou_matrix: IoU 矩阵
+
+ Returns:
+ (匹配的轨迹索引, 匹配的检测索引, 未匹配的轨迹索引, 未匹配的检测索引)
+ """
+ if iou_matrix.size == 0:
+ return [], [], list(range(len(self._tracks))), []
+
+ # 转换为代价矩阵(1 - IoU)
+ cost_matrix = 1 - iou_matrix
+
+ # 匈牙利算法
+ track_indices, det_indices = linear_sum_assignment(cost_matrix)
+
+ matched_tracks: list[int] = []
+ matched_dets: list[int] = []
+ unmatched_tracks: list[int] = list(range(len(self._tracks)))
+ unmatched_dets: list[int] = list(range(iou_matrix.shape[1]))
+
+ for track_idx, det_idx in zip(track_indices, det_indices):
+ # 检查 IoU 是否超过阈值
+ if iou_matrix[track_idx, det_idx] >= self.config.iou_threshold:
+ matched_tracks.append(int(track_idx))
+ matched_dets.append(int(det_idx))
+ unmatched_tracks.remove(int(track_idx))
+ unmatched_dets.remove(int(det_idx))
+
+ return matched_tracks, matched_dets, unmatched_tracks, unmatched_dets
+
+ def _create_track(self, detection: Detection2D) -> _Track:
+ """创建新轨迹。"""
+ track = _Track(
+ track_id=self._next_id,
+ bbox=detection.bbox.astype(np.float32),
+ class_id=detection.class_id,
+ confidence=detection.confidence,
+ )
+ self._tracks.append(track)
+ self._next_id += 1
+
+ logger.debug(f"创建新轨迹 ID={track.track_id}")
+ return track
+
+ def _remove_dead_tracks(self) -> None:
+ """删除死亡轨迹。"""
+ before_count = len(self._tracks)
+ self._tracks = [
+ t for t in self._tracks if t.time_since_update < self.config.max_age
+ ]
+ removed = before_count - len(self._tracks)
+ if removed > 0:
+ logger.debug(f"删除 {removed} 条死亡轨迹")
+
+ def _get_confirmed_tracks(self) -> list[TrackedObject]:
+ """获取确认的轨迹(hits >= min_hits)。"""
+ confirmed = [
+ t.to_tracked_object()
+ for t in self._tracks
+ if t.hits >= self.config.min_hits and t.time_since_update == 0
+ ]
+ logger.debug(f"返回 {len(confirmed)} 条确认轨迹")
+ return confirmed
+
+ def reset(self) -> None:
+ """重置跟踪器状态。"""
+ self._tracks.clear()
+ self._next_id = 1
+ self._frame_count = 0
+ logger.info("跟踪器已重置")
+
+ def get_all_tracks(self) -> list[TrackedObject]:
+ """获取所有轨迹(包括未确认的)。"""
+ return [t.to_tracked_object() for t in self._tracks]
+
+ @property
+ def track_count(self) -> int:
+ """当前轨迹数量。"""
+ return len(self._tracks)
+
+ @property
+ def confirmed_track_count(self) -> int:
+ """确认轨迹数量。"""
+ return len([t for t in self._tracks if t.hits >= self.config.min_hits])
diff --git a/src/aylm/tools/obstacle_marker.py b/src/aylm/tools/obstacle_marker.py
index 4e72cc6..251b4de 100644
--- a/src/aylm/tools/obstacle_marker.py
+++ b/src/aylm/tools/obstacle_marker.py
@@ -74,6 +74,11 @@ class ObstacleBox3D:
label: SemanticLabel # 语义标签
confidence: float # 平均置信度
point_indices: NDArray[np.int64] = field(repr=False) # 属于该障碍物的点索引
+ track_id: Optional[int] = None # 跟踪 ID(跨帧关联)
+ frame_id: Optional[int] = None # 帧 ID(时序关联)
+ timestamp: Optional[float] = None # 时间戳(秒)
+ velocity: Optional[tuple[float, float, float]] = None # 速度向量 (vx, vy, vz) m/s
+ motion_vector: Optional[tuple[float, float, float]] = None # 运动矢量(帧间位移)
@property
def is_movable(self) -> bool:
@@ -140,7 +145,7 @@ def to_dict(self) -> dict:
- center_cv: OpenCV 坐标系 (X右,Y下,Z前)
- center_robot: 机器人坐标系 (X前,Y左,Z上)
"""
- return {
+ result = {
"type": "可运动障碍物" if self.is_movable else "静态障碍物",
"category": LABEL_DESCRIPTIONS.get(self.label, "未知"),
"movable": self.is_movable,
@@ -155,6 +160,24 @@ def to_dict(self) -> dict:
"_label_id": int(self.label.value),
}
+ # 添加跟踪和时序信息
+ if self.track_id is not None:
+ result["track_id"] = self.track_id
+ if self.frame_id is not None:
+ result["frame_id"] = self.frame_id
+ if self.timestamp is not None:
+ result["timestamp"] = self.timestamp
+
+ # 添加运动信息
+ if self.velocity is not None:
+ result["velocity_cv"] = list(self.velocity)
+ result["velocity_robot"] = list(self._cv_to_robot(self.velocity))
+ if self.motion_vector is not None:
+ result["motion_vector_cv"] = list(self.motion_vector)
+ result["motion_vector_robot"] = list(self._cv_to_robot(self.motion_vector))
+
+ return result
+
def get_box_vertices(self) -> NDArray[np.float64]:
"""获取边界框的8个顶点,用于可视化。"""
min_c = self.min_corner
@@ -419,18 +442,29 @@ def highlight_obstacles(
return colors
- def export_to_json(self, obstacles: list[ObstacleBox3D], output_path: Path) -> None:
+ def export_to_json(
+ self,
+ obstacles: list[ObstacleBox3D],
+ output_path: Path,
+ frame_id: Optional[int] = None,
+ timestamp: Optional[float] = None,
+ ) -> None:
"""
导出障碍物列表为 JSON 格式(用于导航系统)。
Args:
obstacles: 障碍物列表
output_path: 输出文件路径
+ frame_id: 帧 ID(可选,用于时序关联)
+ timestamp: 时间戳(可选,秒)
"""
# 统计可运动和静态障碍物数量
movable_count = sum(1 for obs in obstacles if obs.is_movable)
static_count = len(obstacles) - movable_count
+ # 统计有跟踪 ID 的障碍物
+ tracked_count = sum(1 for obs in obstacles if obs.track_id is not None)
+
data = {
"coordinate_systems": {
"cv": {
@@ -446,9 +480,16 @@ def export_to_json(self, obstacles: list[ObstacleBox3D], output_path: Path) -> N
"total_count": len(obstacles),
"movable_count": movable_count,
"static_count": static_count,
+ "tracked_count": tracked_count,
"obstacles": [obs.to_dict() for obs in obstacles],
}
+ # 添加时序元数据
+ if frame_id is not None:
+ data["frame_id"] = frame_id
+ if timestamp is not None:
+ data["timestamp"] = timestamp
+
with open(output_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
diff --git a/src/aylm/tools/pipeline_processor.py b/src/aylm/tools/pipeline_processor.py
index 4fa765e..5131e89 100644
--- a/src/aylm/tools/pipeline_processor.py
+++ b/src/aylm/tools/pipeline_processor.py
@@ -5,6 +5,7 @@
from __future__ import annotations
+import contextlib
import gc
import logging
import threading
@@ -13,10 +14,16 @@
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
-from typing import Callable
+from typing import TYPE_CHECKING, Callable, ClassVar
+
+if TYPE_CHECKING:
+ from torch.nn import Module
+
+ from aylm.tools.object_detector import ObjectDetector
+ from aylm.tools.pointcloud_voxelizer import PointCloudVoxelizer
import torch
-import torch.nn.functional as F
+from torch.nn import functional as functional_nn
# 模块级常量
DEFAULT_VOXEL_SIZE = 0.05 # 5cm 体素
@@ -74,11 +81,14 @@ class PipelineConfig:
async_mode: bool = False
# 切片配置
enable_slice: bool = True # 是否启用切片
- slice_radius: float = 20.0 # 切片半径(米)
+ slice_radius: float = 10.0 # 切片半径(米)
# 语义检测配置
enable_semantic: bool = True # 是否启用语义检测(默认开启)
+ # 输入分辨率配置
+ # 注意:SHARP 模型要求固定 1536,因为内部金字塔结构依赖 1536→768→384
+ internal_resolution: int = 1536 # 内部处理分辨率(固定值,不可更改)
semantic_model: str = "yolo11n-seg.pt" # YOLO 模型
- semantic_confidence: float = 0.5 # 检测置信度
+ semantic_confidence: float = 0.25 # 检测置信度
colorize_semantic: bool = True # 语义着色
# 导航输出配置
output_navigation_ply: bool = True # 是否输出导航用点云(机器人坐标系)
@@ -122,7 +132,7 @@ def avg_voxel_time(self) -> float:
class PipelineLogger:
"""流水线日志记录器。"""
- LEVEL_PREFIX = {
+ LEVEL_PREFIX: ClassVar[dict[str, str]] = {
"INFO": " ",
"STAGE": ">>>",
"OK": " ✓ ",
@@ -131,7 +141,7 @@ class PipelineLogger:
"PROGRESS": " → ",
}
- STATUS_DISPLAY = {
+ STATUS_DISPLAY: ClassVar[dict[TaskStatus, str]] = {
TaskStatus.PENDING: "⏳ 等待中",
TaskStatus.PREDICTING: "🔄 推理中",
TaskStatus.PREDICTED: "📦 待体素化",
@@ -224,11 +234,11 @@ def __init__(self, config: PipelineConfig | None = None):
self.log = PipelineLogger(self.config.verbose)
self.stats = PipelineStats()
- self._predictor = None
+ self._predictor: Module | None = None
self._device: torch.device | None = None
self._model_loaded = False
- self._voxelizer = None
- self._detector = None # YOLO 语义检测器
+ self._voxelizer: PointCloudVoxelizer | None = None
+ self._detector: ObjectDetector | None = None # YOLO 语义检测器
self._tasks: list[ImageTask] = []
self._stop_event = threading.Event()
self._predict_lock = threading.Lock()
@@ -238,7 +248,7 @@ def __init__(self, config: PipelineConfig | None = None):
def __enter__(self):
return self
- def __exit__(self, exc_type, exc_val, exc_tb):
+ def __exit__(self, _exc_type, _exc_val, _exc_tb):
self.cleanup()
return False
@@ -260,10 +270,8 @@ def _unload_model(self):
try:
if self._predictor is not None:
if self._device and self._device.type != "cpu":
- try:
+ with contextlib.suppress(Exception):
self._predictor.cpu()
- except Exception:
- pass
del self._predictor
self._predictor = None
@@ -276,10 +284,8 @@ def _unload_model(self):
torch.cuda.synchronize()
if hasattr(torch, "mps") and hasattr(torch.mps, "empty_cache"):
- try:
+ with contextlib.suppress(Exception):
torch.mps.empty_cache()
- except Exception:
- pass
self.log.ok("模型已卸载,内存已释放")
except Exception as e:
@@ -323,7 +329,7 @@ def _detect_device(self) -> torch.device:
return torch.device(self.config.device)
if torch.cuda.is_available():
return torch.device("cuda")
- if hasattr(torch, "mps") and torch.mps.is_available():
+ if hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")
@@ -353,9 +359,10 @@ def _load_model(self) -> bool:
)
self._predictor = create_predictor(PredictorParams())
- self._predictor.load_state_dict(state_dict)
- self._predictor.eval()
- self._predictor.to(self._device)
+ predictor = self._predictor # 局部变量,mypy 可以推断非 None
+ predictor.load_state_dict(state_dict)
+ predictor.eval()
+ predictor.to(self._device)
self._model_loaded = True
self.log.ok("模型加载完成")
@@ -399,15 +406,17 @@ def _predict_single(self, task: ImageTask, output_dir: Path) -> bool:
self.log.info(f" 图像尺寸: {width}x{height}, 焦距: {f_px:.1f}px")
- # 预处理
- internal_shape = (1536, 1536)
+ # 预处理(使用配置的内部分辨率)
+ res = self.config.internal_resolution
+ internal_shape = (res, res)
+ self.log.info(f" 内部处理分辨率: {res}x{res}")
image_pt = (
torch.from_numpy(image.copy()).float().to(self._device).permute(2, 0, 1)
/ 255.0
)
disparity_factor = torch.tensor([f_px / width]).float().to(self._device)
- image_resized_pt = F.interpolate(
+ image_resized_pt = functional_nn.interpolate(
image_pt[None],
size=(internal_shape[1], internal_shape[0]),
mode="bilinear",
@@ -416,6 +425,7 @@ def _predict_single(self, task: ImageTask, output_dir: Path) -> bool:
# 推理(需要锁保护,因为模型不是线程安全的)
with self._predict_lock:
+ assert self._predictor is not None, "模型未加载"
gaussians_ndc = self._predictor(image_resized_pt, disparity_factor)
# 后处理
@@ -475,26 +485,31 @@ def _predict_single(self, task: ImageTask, output_dir: Path) -> bool:
def _voxelize_single(
self, task: ImageTask, output_dir: Path, navigation_dir: Path | None = None
- ) -> bool:
- """对单个PLY文件进行切片、体素化,可选语义融合。
+ ) -> Path | None:
+ """对单个PLY文件进行切片、体素化。
- 处理流程:SHARP输出 → 切片 → 体素化 → 语义融合
+ 处理流程:SHARP输出 → 切片 → 体素化
+ 语义融合在外部异步执行。
Args:
task: 图像任务
output_dir: 体素化输出目录
- navigation_dir: 导航用点云输出目录(机器人坐标系)
+ navigation_dir: 导航用点云输出目录(未使用,保留兼容性)
+
+ Returns:
+ 体素化后的 PLY 文件路径,失败返回 None
"""
if task.status != TaskStatus.PREDICTED or task.ply_output_path is None:
- return False
+ return None
task.status = TaskStatus.VOXELIZING
task.voxel_start_time = time.time()
- self.log.progress(f"[{task.index+1}] 开始处理: {task.ply_output_path.name}")
+ self.log.progress(f"[{task.index+1}] 开始体素化: {task.ply_output_path.name}")
try:
# 确定输入文件(可能经过切片)
+ assert task.ply_output_path is not None, "PLY 输出路径未设置"
input_ply_path = task.ply_output_path
# 步骤1:切片(如果启用)
@@ -504,6 +519,7 @@ def _voxelize_single(
input_ply_path = sliced_path
# 步骤2:体素化
+ assert self._voxelizer is not None, "体素化器未初始化"
output_path = output_dir / f"vox_{task.ply_output_path.name}"
self._voxelizer.process(
input_ply_path,
@@ -514,35 +530,48 @@ def _voxelize_single(
# 清理临时切片文件
if self.config.enable_slice and input_ply_path != task.ply_output_path:
- try:
+ with contextlib.suppress(Exception):
input_ply_path.unlink()
- except Exception:
- pass
-
- # 步骤3:语义融合(如果启用)
- if self.config.enable_semantic and self._detector is not None:
- self._apply_semantic_fusion(task, output_path, navigation_dir)
task.voxel_output_path = output_path
- task.status = TaskStatus.COMPLETED
- task.voxel_end_time = time.time()
-
- voxel_time = task.voxel_end_time - task.voxel_start_time
+ voxel_time = time.time() - task.voxel_start_time
self.log.ok(
- f"[{task.index+1}] 处理完成: {task.ply_output_path.name} ({voxel_time:.2f}s)"
+ f"[{task.index+1}] 体素化完成: {output_path.name} ({voxel_time:.2f}s)"
)
- self.log.info(f" 输出: {output_path.name}")
- return True
+ return output_path
except Exception as e:
task.status = TaskStatus.FAILED
task.error_message = str(e)
task.voxel_end_time = time.time()
- self.log.error(f"[{task.index+1}] 处理失败: {task.ply_output_path.name}")
- self.log.error(f" 错误类型: {type(e).__name__}")
- self.log.error(f" 错误信息: {e}")
- logger.exception(f"处理异常详情 - {task.ply_output_path.name}")
+ self.log.error(f"[{task.index+1}] 体素化失败: {task.ply_output_path.name}")
+ self.log.error(f" 错误: {e}")
+ logger.exception(f"体素化异常 - {task.ply_output_path.name}")
+ return None
+
+ def _semantic_and_navigation(
+ self, task: ImageTask, voxel_path: Path, navigation_dir: Path | None = None
+ ) -> bool:
+ """执行语义检测和导航输出(可异步调用)。
+
+ Args:
+ task: 图像任务
+ voxel_path: 体素化后的 PLY 文件路径
+ navigation_dir: 导航输出目录
+
+ Returns:
+ 是否成功
+ """
+ try:
+ self._apply_semantic_fusion(task, voxel_path, navigation_dir)
+ task.status = TaskStatus.COMPLETED
+ task.voxel_end_time = time.time()
+ return True
+ except Exception as e:
+ self.log.error(f"[{task.index+1}] 语义处理失败: {e}")
+ task.status = TaskStatus.COMPLETED # 体素化成功,语义失败不算整体失败
+ task.voxel_end_time = time.time()
return False
def _apply_slice(self, task: ImageTask) -> Path | None:
@@ -556,6 +585,10 @@ def _apply_slice(self, task: ImageTask) -> Path | None:
"""
import numpy as np
+ if task.ply_output_path is None:
+ self.log.warn(" PLY 输出路径未设置,跳过切片")
+ return None
+
try:
from plyfile import PlyData, PlyElement
except ImportError:
@@ -646,6 +679,7 @@ def _apply_semantic_fusion(
height, width = image.shape[:2]
# 执行目标检测
+ assert self._detector is not None, "检测器未初始化"
detections = self._detector.detect(image, return_masks=True)
self.log.info(f" 检测到 {len(detections)} 个目标")
@@ -802,7 +836,7 @@ def _collect_images(self, input_path: Path) -> list[Path]:
if input_path.is_file():
return [input_path] if input_path.suffix.lower() in extensions else []
- images = []
+ images: list[Path] = []
for ext in extensions:
images.extend(input_path.glob(f"*{ext}"))
images.extend(input_path.glob(f"*{ext.upper()}"))
@@ -1019,10 +1053,10 @@ def _execute_pipeline(
):
"""执行流水线处理逻辑。
- 流水线策略:
- 1. 第一张图片: 只做推理(无并行)
- 2. 第2到N张图片: 推理第N张 || 体素化第N-1张(并行)
- 3. 最后: 体素化最后一张图片(无并行)
+ 三级并行流水线:
+ - 推理(N): 主线程执行 SHARP 推理
+ - 体素化(N-1): 线程1 执行体素化
+ - 语义检测(N-2): 线程2 执行语义检测和导航输出
时间线示意:
图片1: [====推理====]
@@ -1030,8 +1064,11 @@ def _execute_pipeline(
图片1: [====体素化====]
图片3: [====推理====]
图片2: [====体素化====]
+ 图片1: [====语义====]
...
+ 每帧完成语义检测后立即输出导航点云,实现快速响应。
+
Args:
output_dir: PLY 输出目录
voxel_output_dir: 体素化输出目录
@@ -1043,57 +1080,117 @@ def _execute_pipeline(
self.log.warn("没有任务需要处理")
return
- # 使用线程池执行体素化(推理在主线程,因为GPU操作需要同步)
- with ThreadPoolExecutor(max_workers=1) as voxel_executor:
+ # 使用两个线程池:体素化和语义检测
+ with (
+ ThreadPoolExecutor(
+ max_workers=1, thread_name_prefix="voxel"
+ ) as voxel_executor,
+ ThreadPoolExecutor(
+ max_workers=1, thread_name_prefix="semantic"
+ ) as semantic_executor,
+ ):
+
voxel_future: Future | None = None
- prev_task_for_voxel: ImageTask | None = None
+ semantic_future: Future | None = None
+ current_voxel_task: ImageTask | None = None # 当前正在体素化的任务
+
+ # 待处理队列
+ pending_voxel_task: ImageTask | None = None # 等待体素化的任务
+ pending_semantic: tuple[ImageTask, Path] | None = None # 等待语义检测
for i, task in enumerate(self._tasks):
self.log.info(f"\n{'─' * 40}")
self.log.info(f"处理进度: {i+1}/{total}")
- # 显示当前阶段的并行状态
- if i == 0:
- self.log.info(" 阶段: 推理第1张(无并行)")
- elif i < total:
- self.log.info(f" 阶段: 推理第{i+1}张 || 体素化第{i}张(并行)")
+ # 显示当前并行状态
+ parallel_info = []
+ if pending_voxel_task is not None:
+ parallel_info.append(f"体素化第{pending_voxel_task.index+1}张")
+ if pending_semantic is not None and self.config.enable_semantic:
+ parallel_info.append(f"语义第{pending_semantic[0].index+1}张")
+ if parallel_info:
+ self.log.info(
+ f" 并行: 推理第{i+1}张 || {' || '.join(parallel_info)}"
+ )
+ else:
+ self.log.info(f" 阶段: 推理第{i+1}张")
+
+ # 启动语义检测(如果有待处理的)
+ if pending_semantic is not None and self.config.enable_semantic:
+ sem_task, sem_voxel_path = pending_semantic
+ self.log.progress(
+ f" 启动语义检测: [{sem_task.index+1}] {sem_task.image_path.name}"
+ )
+ semantic_future = semantic_executor.submit(
+ self._semantic_and_navigation,
+ sem_task,
+ sem_voxel_path,
+ navigation_dir,
+ )
+ pending_semantic = None
- # 如果有上一张图片需要体素化,启动异步体素化
- if prev_task_for_voxel is not None:
+ # 启动体素化(如果有待处理的)
+ if pending_voxel_task is not None:
+ current_voxel_task = pending_voxel_task
self.log.progress(
- f" 启动并行体素化: [{prev_task_for_voxel.index+1}] {prev_task_for_voxel.image_path.name}"
+ f" 启动体素化: [{current_voxel_task.index+1}] {current_voxel_task.image_path.name}"
)
voxel_future = voxel_executor.submit(
self._voxelize_single,
- prev_task_for_voxel,
+ current_voxel_task,
voxel_output_dir,
navigation_dir,
)
+ pending_voxel_task = None
# 执行当前图片的推理(主线程)
predict_success = self._predict_single(task, output_dir)
- # 等待并行的体素化完成(如果有)
+ # 等待体素化完成,获取结果用于语义检测
if voxel_future is not None:
try:
- voxel_future.result()
+ voxel_path = voxel_future.result()
+ if voxel_path is not None and current_voxel_task is not None:
+ # 将体素化结果加入语义检测队列
+ pending_semantic = (current_voxel_task, voxel_path)
except Exception as e:
self.log.error(f"体素化任务异常: {e}")
voxel_future = None
+ current_voxel_task = None
# 记录当前任务用于下一轮的体素化
if predict_success:
- prev_task_for_voxel = task
- else:
- prev_task_for_voxel = None
+ pending_voxel_task = task
- # 处理最后一张图片的体素化(同步执行,无并行)
- if prev_task_for_voxel is not None:
+ # 处理剩余的体素化任务
+ if pending_voxel_task is not None:
self.log.info(f"\n{'─' * 40}")
- self.log.info("最终阶段: 体素化最后一张图片")
- self._voxelize_single(
- prev_task_for_voxel, voxel_output_dir, navigation_dir
+ self.log.info("收尾阶段: 处理剩余任务")
+
+ self.log.progress(
+ f" 体素化: [{pending_voxel_task.index+1}] {pending_voxel_task.image_path.name}"
+ )
+ voxel_path = self._voxelize_single(
+ pending_voxel_task, voxel_output_dir, navigation_dir
+ )
+
+ if voxel_path is not None:
+ pending_semantic = (pending_voxel_task, voxel_path)
+
+ # 等待最后的语义检测完成
+ if semantic_future is not None:
+ try:
+ semantic_future.result()
+ except Exception as e:
+ self.log.error(f"语义检测任务异常: {e}")
+
+ # 处理最后一个语义检测
+ if pending_semantic is not None and self.config.enable_semantic:
+ sem_task, sem_voxel_path = pending_semantic
+ self.log.progress(
+ f" 语义检测: [{sem_task.index+1}] {sem_task.image_path.name}"
)
+ self._semantic_and_navigation(sem_task, sem_voxel_path, navigation_dir)
def run_pipeline(
diff --git a/src/aylm/tools/pointcloud_voxelizer.py b/src/aylm/tools/pointcloud_voxelizer.py
index aa94351..7c47fac 100644
--- a/src/aylm/tools/pointcloud_voxelizer.py
+++ b/src/aylm/tools/pointcloud_voxelizer.py
@@ -79,6 +79,62 @@ def filter_by_mask(self, mask: "NDArray[np.bool_]") -> "PointCloud":
colors = self.colors[mask] if self.colors is not None else None
return PointCloud(points=self.points[mask], colors=colors)
+ @classmethod
+ def from_ply(cls, path: "Path") -> "PointCloud":
+ """从 PLY 文件加载点云。"""
+ ply = PlyData.read(str(path))
+ vertex = ply["vertex"]
+
+ points = np.column_stack([vertex["x"], vertex["y"], vertex["z"]])
+
+ colors = None
+ if "red" in vertex.data.dtype.names:
+ colors = np.column_stack(
+ [vertex["red"], vertex["green"], vertex["blue"]]
+ ).astype(np.float64)
+ if colors.max() > 1.0:
+ colors = colors / 255.0
+
+ return cls(points=points.astype(np.float64), colors=colors)
+
+ def to_ply(self, path: "Path") -> None:
+ """保存点云到 PLY 文件。"""
+ path = Path(path)
+ path.parent.mkdir(parents=True, exist_ok=True)
+
+ n = len(self.points)
+ if self.colors is not None:
+ colors = self.colors
+ if colors.max() <= 1.0:
+ colors = (colors * 255).astype(np.uint8)
+ else:
+ colors = colors.astype(np.uint8)
+
+ dtype = [
+ ("x", "f4"),
+ ("y", "f4"),
+ ("z", "f4"),
+ ("red", "u1"),
+ ("green", "u1"),
+ ("blue", "u1"),
+ ]
+ data = np.zeros(n, dtype=dtype)
+ data["x"] = self.points[:, 0]
+ data["y"] = self.points[:, 1]
+ data["z"] = self.points[:, 2]
+ data["red"] = colors[:, 0]
+ data["green"] = colors[:, 1]
+ data["blue"] = colors[:, 2]
+ else:
+ dtype = [("x", "f4"), ("y", "f4"), ("z", "f4")]
+ data = np.zeros(n, dtype=dtype)
+ data["x"] = self.points[:, 0]
+ data["y"] = self.points[:, 1]
+ data["z"] = self.points[:, 2]
+
+ vertex = PlyElement.describe(data, "vertex")
+ PlyData([vertex], text=False).write(str(path))
+
class PointCloudVoxelizer:
"""点云体素化处理器。"""
@@ -100,9 +156,12 @@ def _get_device(self) -> str:
return GPU_DEVICE
elif device_config == "cuda" and torch.cuda.is_available():
return "cuda"
- elif device_config == "mps" and hasattr(torch.backends, "mps"):
- if torch.backends.mps.is_available():
- return "mps"
+ elif (
+ device_config == "mps"
+ and hasattr(torch.backends, "mps")
+ and torch.backends.mps.is_available()
+ ):
+ return "mps"
return "cpu"
def _should_use_gpu(self) -> bool:
@@ -169,7 +228,7 @@ def _remove_outliers_o3d(self, pc: PointCloud, cfg: VoxelizerConfig) -> PointClo
"""使用Open3D去除离群点。"""
pcd = self._to_o3d(pc)
- pcd_clean, indices = pcd.remove_statistical_outlier(
+ _pcd_clean, indices = pcd.remove_statistical_outlier(
nb_neighbors=cfg.statistical_nb_neighbors,
std_ratio=cfg.statistical_std_ratio,
)
@@ -514,7 +573,7 @@ def _detect_ground_gpu(
# 找最佳平面
best_idx = inliers_count.argmax()
if inliers_count[best_idx] > best_inlier_count:
- best_inlier_count = inliers_count[best_idx].item()
+ best_inlier_count = int(inliers_count[best_idx].item())
best_inliers_mask = (
distances[:, best_idx] < cfg.ransac_distance_threshold
)
@@ -562,12 +621,12 @@ def _voxel_downsample_numpy(self, pc: PointCloud) -> PointCloud:
for i, key in enumerate(map(tuple, voxel_indices)):
voxel_dict.setdefault(key, []).append(i)
- new_points = []
- new_colors = [] if pc.colors is not None else None
+ new_points: list[np.ndarray] = []
+ new_colors: list[np.ndarray] | None = [] if pc.colors is not None else None
for indices in voxel_dict.values():
new_points.append(pc.points[indices].mean(axis=0))
- if pc.colors is not None:
+ if pc.colors is not None and new_colors is not None:
new_colors.append(pc.colors[indices].mean(axis=0))
points = np.array(new_points)
diff --git a/src/aylm/tools/semantic_fusion.py b/src/aylm/tools/semantic_fusion.py
index f3af4fc..adf74ce 100644
--- a/src/aylm/tools/semantic_fusion.py
+++ b/src/aylm/tools/semantic_fusion.py
@@ -334,7 +334,7 @@ def save_navigation_ply(
colors = semantic_pc.colorize_by_semantic()
# 体素化:基于密度阈值生成实体方块信息
- voxel_centers, voxel_colors, voxel_labels, _, _ = self._create_solid_voxels(
+ voxel_centers, voxel_colors, _voxel_labels, _, _ = self._create_solid_voxels(
robot_points,
colors,
semantic_pc.labels,
@@ -499,7 +499,8 @@ def _create_solid_voxels(
# 使用 defaultdict 聚合每个体素的点索引
voxel_dict: dict[tuple[int, int, int], list[int]] = defaultdict(list)
for i, idx in enumerate(voxel_indices):
- voxel_dict[tuple(int(x) for x in idx)].append(i)
+ key: tuple[int, int, int] = (int(idx[0]), int(idx[1]), int(idx[2]))
+ voxel_dict[key].append(i)
# 过滤:只保留点数达到阈值的体素
solid_voxels = {k: v for k, v in voxel_dict.items() if len(v) >= min_points}
diff --git a/src/aylm/tools/semantic_types.py b/src/aylm/tools/semantic_types.py
index f706963..8b6c669 100644
--- a/src/aylm/tools/semantic_types.py
+++ b/src/aylm/tools/semantic_types.py
@@ -61,6 +61,8 @@ class Detection2D:
class_id: int # COCO 类别 ID
confidence: float # 置信度
semantic_label: SemanticLabel # 语义标签
+ track_id: Optional[int] = None # 跟踪 ID(跨帧关联)
+ frame_id: Optional[int] = None # 帧 ID(时序关联)
@property
def area(self) -> float:
@@ -116,11 +118,11 @@ class CameraIntrinsics:
cy: float # 主点 y 坐标(像素)
@classmethod
- def from_matrix(cls, K: NDArray[np.float64]) -> "CameraIntrinsics":
+ def from_matrix(cls, k: NDArray[np.float64]) -> "CameraIntrinsics":
"""从 3x3 内参矩阵创建。
Args:
- K: 3x3 相机内参矩阵
+ k: 3x3 相机内参矩阵
[[fx, 0, cx],
[ 0, fy, cy],
[ 0, 0, 1]]
@@ -128,7 +130,7 @@ def from_matrix(cls, K: NDArray[np.float64]) -> "CameraIntrinsics":
Returns:
CameraIntrinsics 实例
"""
- return cls(fx=K[0, 0], fy=K[1, 1], cx=K[0, 2], cy=K[1, 2])
+ return cls(fx=k[0, 0], fy=k[1, 1], cx=k[0, 2], cy=k[1, 2])
@classmethod
def from_focal_length(
diff --git a/src/aylm/tools/video_config.py b/src/aylm/tools/video_config.py
index f08218d..af89399 100644
--- a/src/aylm/tools/video_config.py
+++ b/src/aylm/tools/video_config.py
@@ -5,6 +5,7 @@
import logging
from dataclasses import asdict
+from enum import Enum
from pathlib import Path
from typing import Any
@@ -50,7 +51,7 @@ def _validate_range(value: Any, min_val: float, max_val: float, name: str) -> No
)
-def _validate_enum(value: str, enum_class: type, name: str) -> None:
+def _validate_enum(value: str, enum_class: type[Enum], name: str) -> None:
"""验证枚举值。"""
valid_values = [e.value for e in enum_class]
if value not in valid_values:
diff --git a/src/aylm/tools/video_extractor.py b/src/aylm/tools/video_extractor.py
index 30c2114..9b1e8da 100644
--- a/src/aylm/tools/video_extractor.py
+++ b/src/aylm/tools/video_extractor.py
@@ -9,10 +9,9 @@
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
-from typing import TYPE_CHECKING, Callable
+from typing import Any, Callable
import cv2
-import numpy as np
from .video_types import (
FrameExtractionMethod,
@@ -22,9 +21,6 @@
VideoMetadata,
)
-if TYPE_CHECKING:
- from numpy.typing import NDArray
-
logger = logging.getLogger(__name__)
ProgressCallback = Callable[[int, int, float], None]
@@ -73,31 +69,36 @@ def _calculate_frame_indices(
fps, total_frames = metadata.fps, metadata.total_frames
# 计算帧间隔
+ interval_float: float
if method == FrameExtractionMethod.INTERVAL:
- interval = max(1, int(self.config.frame_interval * fps))
+ interval_float = float(max(1, int(self.config.frame_interval * fps)))
elif (
method == FrameExtractionMethod.UNIFORM
and self.config.target_fps
and self.config.target_fps < fps
):
- interval = fps / self.config.target_fps
+ interval_float = fps / self.config.target_fps
else:
- interval = max(1, int(fps)) # KEYFRAME 和 SCENE_CHANGE 默认每秒一帧
+ interval_float = float(
+ max(1, int(fps))
+ ) # KEYFRAME 和 SCENE_CHANGE 默认每秒一帧
# 生成帧索引
if method == FrameExtractionMethod.UNIFORM and (
not self.config.target_fps or self.config.target_fps >= fps
):
indices = [(i, i / fps) for i in range(total_frames)]
- elif isinstance(interval, float):
+ elif interval_float != int(interval_float):
+ # 非整数间隔,使用浮点累加
indices = []
i = 0.0
while int(i) < total_frames:
frame_idx = int(i)
indices.append((frame_idx, frame_idx / fps))
- i += interval
+ i += interval_float
else:
- indices = [(i, i / fps) for i in range(0, total_frames, interval)]
+ interval_int = int(interval_float)
+ indices = [(i, i / fps) for i in range(0, total_frames, interval_int)]
# 限制最大帧数
if self.config.max_frames:
@@ -105,7 +106,7 @@ def _calculate_frame_indices(
return indices
- def compress_frame(self, frame: NDArray[np.uint8]) -> NDArray[np.uint8]:
+ def compress_frame(self, frame: Any) -> Any:
"""压缩帧图像(调整大小)。"""
if self.config.resize_width is None and self.config.resize_height is None:
return frame
@@ -153,13 +154,13 @@ def extract_frame(
try:
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_index)
ret, frame = cap.read()
- if not ret:
+ if not ret or frame is None:
logger.warning(f"Cannot read frame {frame_index} from {video_path}")
return None
- frame = self.compress_frame(frame)
+ compressed = self.compress_frame(frame)
output_path.parent.mkdir(parents=True, exist_ok=True)
- cv2.imwrite(str(output_path), frame, self._get_image_write_params())
+ cv2.imwrite(str(output_path), compressed, self._get_image_write_params())
fps = cap.get(cv2.CAP_PROP_FPS)
return FrameInfo(
@@ -190,13 +191,13 @@ def _extract_frames_batch(
for frame_index, timestamp in frame_indices:
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_index)
ret, frame = cap.read()
- if not ret:
+ if not ret or frame is None:
logger.warning(f"Cannot read frame {frame_index} from {video_path}")
continue
- frame = self.compress_frame(frame)
+ compressed = self.compress_frame(frame)
output_path = output_dir / f"frame_{frame_index:06d}.{output_format}"
- cv2.imwrite(str(output_path), frame, params)
+ cv2.imwrite(str(output_path), compressed, params)
frames.append(
FrameInfo(
@@ -233,7 +234,7 @@ def extract_frames(
start_time = time.time()
metadata, output_dir, error = self._prepare_extraction(video_path, output_dir)
- if error:
+ if error or metadata is None:
return FrameExtractionResult(
video_path=video_path, output_dir=output_dir, error_message=error
)
@@ -270,7 +271,7 @@ def extract_frames_async(
start_time = time.time()
metadata, output_dir, error = self._prepare_extraction(video_path, output_dir)
- if error:
+ if error or metadata is None:
return FrameExtractionResult(
video_path=video_path, output_dir=output_dir, error_message=error
)
diff --git a/src/aylm/tools/video_pipeline.py b/src/aylm/tools/video_pipeline.py
index 8caa0c6..c85f4f9 100644
--- a/src/aylm/tools/video_pipeline.py
+++ b/src/aylm/tools/video_pipeline.py
@@ -1,9 +1,11 @@
"""视频处理流水线模块。
-实现帧提取与处理的并行流水线:帧提取、SHARP推理、体素化、语义融合。
+实现帧提取与处理的并行流水线:帧提取、SHARP推理、体素化、语义融合、目标跟踪。
"""
+import contextlib
import gc
+import json
import logging
import queue
import threading
@@ -11,9 +13,13 @@
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
+from typing import Optional
+import numpy as np
import torch
+from .motion_estimator import MotionEstimator
+from .object_tracker import MultiObjectTracker, TrackedObject, TrackerConfig
from .pointcloud_voxelizer import PointCloudVoxelizer, VoxelizerConfig
from .video_config import load_or_create_config
from .video_extractor import VideoExtractor
@@ -48,10 +54,24 @@ class VideoPipelineConfig:
# 语义检测配置
enable_semantic: bool = True # 是否启用语义检测
semantic_model: str = "yolo11n-seg.pt" # YOLO 模型
- semantic_confidence: float = 0.5 # 检测置信度阈值
+ semantic_confidence: float = 0.25 # 检测置信度阈值
colorize_semantic: bool = True # 是否根据语义标签着色
+ # 点云切片配置
+ enable_slice: bool = True # 是否启用点云切片
+ slice_radius: float = 10.0 # 切片半径(米)
# 导航输出配置
output_navigation_ply: bool = True # 是否输出导航用点云(机器人坐标系)
+ # 输入分辨率配置
+ # 注意:SHARP 模型要求固定 1536,因为内部金字塔结构依赖 1536→768→384
+ internal_resolution: int = 1536 # 内部处理分辨率(固定值,不可更改)
+ # 目标跟踪配置
+ enable_tracking: bool = True # 是否启用目标跟踪
+ tracker_max_age: int = 30 # 轨迹最大存活帧数
+ tracker_min_hits: int = 3 # 轨迹确认所需最小匹配次数
+ tracker_iou_threshold: float = 0.3 # IoU 匹配阈值
+ # 运动估计配置
+ motion_fps: float = 30.0 # 帧率(用于运动估计)
+ motion_stationary_threshold: float = 0.1 # 静止判定阈值(m/s)
class VideoPipelineProcessor:
@@ -73,6 +93,8 @@ def __init__(self, config: VideoPipelineConfig | None = None):
self._model_loaded = False
self._voxelizer: PointCloudVoxelizer | None = None
self._detector = None # YOLO 语义检测器
+ self._tracker: Optional[MultiObjectTracker] = None # 目标跟踪器
+ self._motion_estimator: Optional[MotionEstimator] = None # 运动估计器
self._frame_queue: queue.Queue[FrameInfo] = queue.Queue(
maxsize=self.config.frame_queue_size
)
@@ -85,7 +107,7 @@ def _detect_device(self) -> torch.device:
return torch.device(self.config.device)
if torch.cuda.is_available():
return torch.device("cuda")
- if hasattr(torch, "mps") and torch.mps.is_available():
+ if hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")
@@ -116,10 +138,11 @@ def _load_model(self) -> bool:
model_url, progress=True, map_location=self._device
)
- self._predictor = create_predictor(PredictorParams())
- self._predictor.load_state_dict(state_dict)
- self._predictor.eval()
- self._predictor.to(self._device)
+ predictor = create_predictor(PredictorParams())
+ predictor.load_state_dict(state_dict)
+ predictor.eval()
+ predictor.to(self._device)
+ self._predictor = predictor
self._model_loaded = True
logger.info("Model loaded successfully")
@@ -136,10 +159,8 @@ def _unload_model(self):
logger.info("Unloading model...")
if self._predictor is not None:
if self._device and self._device.type != "cpu":
- try:
+ with contextlib.suppress(Exception):
self._predictor.cpu()
- except Exception:
- pass
del self._predictor
self._predictor = None
self._device = None
@@ -180,6 +201,96 @@ def _cleanup_detector(self):
del self._detector
self._detector = None
+ def _load_tracker(self):
+ """加载目标跟踪器和运动估计器。"""
+ logger.info("Initializing object tracker and motion estimator...")
+ tracker_config = TrackerConfig(
+ max_age=self.config.tracker_max_age,
+ min_hits=self.config.tracker_min_hits,
+ iou_threshold=self.config.tracker_iou_threshold,
+ )
+ self._tracker = MultiObjectTracker(config=tracker_config)
+ self._motion_estimator = MotionEstimator(
+ fps=self.config.motion_fps,
+ stationary_threshold=self.config.motion_stationary_threshold,
+ )
+ logger.info(
+ f"Tracker initialized (max_age={self.config.tracker_max_age}, "
+ f"min_hits={self.config.tracker_min_hits})"
+ )
+
+ def _cleanup_tracker(self):
+ """清理跟踪器和运动估计器。"""
+ if self._tracker is not None:
+ logger.info("Cleaning up tracker...")
+ self._tracker.reset()
+ del self._tracker
+ self._tracker = None
+ if self._motion_estimator is not None:
+ self._motion_estimator.clear()
+ del self._motion_estimator
+ self._motion_estimator = None
+
+ def _apply_slice(self, ply_path: Path, frame_stem: str) -> Path | None:
+ """对点云执行半径切片,只保留摄像机附近的点。
+
+ Args:
+ ply_path: 原始 PLY 文件路径
+ frame_stem: 帧文件名(不含扩展名)
+
+ Returns:
+ 切片后的临时 PLY 文件路径,失败返回 None
+ """
+ import numpy as np
+
+ try:
+ from plyfile import PlyData, PlyElement
+ except ImportError:
+ logger.warning("plyfile not installed, skipping slice")
+ return None
+
+ logger.info(f"Applying slice (radius: {self.config.slice_radius}m)")
+
+ try:
+ # 读取点云
+ ply_data = PlyData.read(str(ply_path))
+ vertex = ply_data["vertex"]
+
+ # 获取坐标
+ x = np.array(vertex["x"], dtype=np.float64)
+ z = np.array(vertex["z"], dtype=np.float64)
+
+ # 计算水平距离(X-Z平面,以原点为圆心)
+ horizontal_dist = np.sqrt(x**2 + z**2)
+
+ # 筛选在半径内的点
+ mask = horizontal_dist <= self.config.slice_radius
+ kept_count = mask.sum()
+ total_count = len(mask)
+
+ if kept_count == 0:
+ logger.warning("No points within slice radius, skipping slice")
+ return None
+
+ logger.info(
+ f" Slice: {kept_count}/{total_count} points "
+ f"({100*kept_count/total_count:.1f}%)"
+ )
+
+ # 创建新的顶点数据
+ new_vertex_data = vertex.data[mask]
+ new_vertex = PlyElement.describe(new_vertex_data, "vertex")
+
+ # 保存到临时文件
+ sliced_path = ply_path.parent / f"sliced_{frame_stem}.ply"
+ PlyData([new_vertex], text=False).write(str(sliced_path))
+
+ return sliced_path
+
+ except Exception as e:
+ logger.warning(f"Failed to apply slice: {e}")
+ return None
+
def _apply_semantic_fusion(
self,
frame_path: Path,
@@ -187,8 +298,10 @@ def _apply_semantic_fusion(
detections_dir: Path,
navigation_dir: Path | None = None,
focal_length: float | None = None,
+ frame_id: int = 0,
+ timestamp: float = 0.0,
) -> None:
- """对体素化后的点云应用语义融合。
+ """对体素化后的点云应用语义融合和目标跟踪。
Args:
frame_path: 原始帧图像路径
@@ -196,9 +309,10 @@ def _apply_semantic_fusion(
detections_dir: 检测结果图片保存目录
navigation_dir: 导航用点云输出目录(机器人坐标系)
focal_length: SHARP 推理时使用的焦距(像素),如果为 None 则估算
+ frame_id: 帧 ID(用于跟踪)
+ timestamp: 时间戳(秒,用于运动估计)
"""
import cv2
- import numpy as np
from aylm.tools.semantic_fusion import FusionConfig, SemanticFusion
from aylm.tools.semantic_types import CameraIntrinsics
@@ -217,8 +331,14 @@ def _apply_semantic_fusion(
height, width = image.shape[:2]
# 执行目标检测
+ assert self._detector is not None, "Detector not initialized"
detections = self._detector.detect(image, return_masks=True)
- logger.debug(f"Detected {len(detections)} objects")
+ logger.info(f"Detected {len(detections)} objects in {frame_path.name}")
+ for det in detections:
+ logger.debug(
+ f" - {det.semantic_label.name} (class_id={det.class_id}, "
+ f"conf={det.confidence:.2f})"
+ )
# 保存检测结果可视化图片
if detections:
@@ -228,6 +348,15 @@ def _apply_semantic_fusion(
)
logger.debug(f"Detection image saved: {detection_image_path.name}")
+ # 目标跟踪(如果启用)
+ tracked_objects: list[TrackedObject] = []
+ if self.config.enable_tracking and self._tracker is not None:
+ tracked_objects = self._tracker.update(detections, frame_id)
+ logger.info(
+ f"Tracking: {len(tracked_objects)} confirmed tracks "
+ f"(total: {self._tracker.track_count})"
+ )
+
if not detections:
logger.debug("No detections, skipping semantic fusion")
return
@@ -297,11 +426,22 @@ def _apply_semantic_fusion(
intrinsics=intrinsics,
)
+ # 计算运动信息并导出带跟踪信息的 JSON
if obstacles:
json_path = (
voxel_ply_path.parent / f"{voxel_ply_path.stem}_obstacles.json"
)
- marker.export_to_json(obstacles, json_path)
+ self._export_obstacles_with_tracking(
+ obstacles=obstacles,
+ tracked_objects=tracked_objects,
+ detections=detections,
+ points=points,
+ intrinsics=intrinsics,
+ image_shape=(height, width),
+ frame_id=frame_id,
+ timestamp=timestamp,
+ output_path=json_path,
+ )
logger.debug(f"Obstacles exported: {json_path.name}")
# 输出导航用点云(机器人坐标系)
@@ -314,6 +454,152 @@ def _apply_semantic_fusion(
logger.warning(f"Semantic fusion failed: {e}")
logger.exception(f"Semantic fusion error - {frame_path.name}")
+ def _export_obstacles_with_tracking(
+ self,
+ obstacles: list,
+ tracked_objects: list[TrackedObject],
+ detections: list,
+ points: np.ndarray,
+ intrinsics,
+ image_shape: tuple[int, int],
+ frame_id: int,
+ timestamp: float,
+ output_path: Path,
+ ) -> None:
+ """导出带跟踪和运动信息的障碍物 JSON。
+
+ Args:
+ obstacles: 障碍物列表
+ tracked_objects: 跟踪对象列表
+ detections: 检测结果列表
+ points: 3D 点云
+ intrinsics: 相机内参
+ image_shape: 图像尺寸 (height, width)
+ frame_id: 帧 ID
+ timestamp: 时间戳
+ output_path: 输出路径
+ """
+ from aylm.tools.coordinate_utils import opencv_to_robot
+ from aylm.tools.obstacle_marker import MOVABLE_LABELS
+
+ # 建立检测到跟踪对象的映射(基于 IoU)
+ det_to_track: dict[int, TrackedObject] = {}
+ if tracked_objects:
+ for det_idx, det in enumerate(detections):
+ best_iou = 0.0
+ best_track = None
+ for track in tracked_objects:
+ iou = self._compute_iou(det.bbox, track.bbox)
+ if iou > best_iou and iou > 0.3:
+ best_iou = iou
+ best_track = track
+ if best_track is not None:
+ det_to_track[det_idx] = best_track
+
+ # 构建带运动信息的障碍物数据
+ obstacles_data = []
+ for obs_idx, obs in enumerate(obstacles):
+ obs_dict = obs.to_dict()
+
+ # 查找对应的跟踪对象
+ track = det_to_track.get(obs_idx)
+ if track is not None:
+ obs_dict["track_id"] = track.track_id
+ obs_dict["track_age"] = track.age
+ obs_dict["track_hits"] = track.hits
+
+ # 计算 3D 中心点并更新运动估计
+ center_cv = np.array(obs.center, dtype=np.float32)
+ if self._motion_estimator is not None:
+ motion = self._motion_estimator.update(
+ track_id=track.track_id,
+ position_cv=center_cv,
+ frame_id=frame_id,
+ timestamp=timestamp,
+ )
+ if motion is not None:
+ obs_dict["motion"] = {
+ "velocity_cv": motion.velocity_cv.tolist(),
+ "velocity_robot": motion.velocity_robot.tolist(),
+ "speed": motion.speed,
+ "heading": motion.heading,
+ "is_stationary": motion.is_stationary,
+ }
+ logger.debug(
+ f"Track {track.track_id}: speed={motion.speed:.2f}m/s, "
+ f"stationary={motion.is_stationary}"
+ )
+
+ # 预测未来位置(1秒后)
+ predicted_pos = self._motion_estimator.predict(
+ track.track_id, dt=1.0
+ )
+ if predicted_pos is not None:
+ predicted_robot = opencv_to_robot(predicted_pos)
+ obs_dict["predicted_position_1s"] = {
+ "cv": predicted_pos.tolist(),
+ "robot": predicted_robot.tolist(),
+ }
+ else:
+ obs_dict["track_id"] = None
+
+ obstacles_data.append(obs_dict)
+
+ # 统计
+ movable_count = sum(1 for obs in obstacles if obs.label in MOVABLE_LABELS)
+ static_count = len(obstacles) - movable_count
+ tracked_count = sum(1 for d in obstacles_data if d.get("track_id") is not None)
+
+ data = {
+ "frame_id": frame_id,
+ "timestamp": timestamp,
+ "coordinate_systems": {
+ "cv": {
+ "description": "OpenCV/相机坐标系",
+ "axes": "X右, Y下, Z前",
+ },
+ "robot": {
+ "description": "机器人/ROS坐标系",
+ "axes": "X前, Y左, Z上",
+ },
+ "transform": "X_robot=Z_cv, Y_robot=-X_cv, Z_robot=-Y_cv",
+ },
+ "total_count": len(obstacles),
+ "movable_count": movable_count,
+ "static_count": static_count,
+ "tracked_count": tracked_count,
+ "obstacles": obstacles_data,
+ }
+
+ with open(output_path, "w", encoding="utf-8") as f:
+ json.dump(data, f, indent=2, ensure_ascii=False)
+
+ logger.info(
+ f"Exported {len(obstacles)} obstacles ({tracked_count} tracked) "
+ f"to {output_path.name}"
+ )
+
+ @staticmethod
+ def _compute_iou(bbox1: np.ndarray, bbox2: np.ndarray) -> float:
+ """计算两个边界框的 IoU。"""
+ x1 = max(bbox1[0], bbox2[0])
+ y1 = max(bbox1[1], bbox2[1])
+ x2 = min(bbox1[2], bbox2[2])
+ y2 = min(bbox1[3], bbox2[3])
+
+ if x2 <= x1 or y2 <= y1:
+ return 0.0
+
+ intersection = (x2 - x1) * (y2 - y1)
+ area1 = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
+ area2 = (bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1])
+ union = area1 + area2 - intersection
+
+ if union <= 0:
+ return 0.0
+
+ return float(intersection / union)
+
def _save_navigation_ply(
self,
semantic_pc,
@@ -365,7 +651,7 @@ def _extraction_worker(
)
# 逐帧提取并放入队列
- for frame_index, timestamp in frame_indices:
+ for frame_index, _timestamp in frame_indices:
if self._stop_event.is_set():
break
output_format = video_config.output_format.lower()
@@ -389,8 +675,9 @@ def _process_frame(
voxel_output_dir: Path,
detections_dir: Path | None = None,
navigation_dir: Path | None = None,
+ frame_id: int = 0,
) -> bool:
- """处理单帧:推理 + 体素化 + 语义融合(可选)。
+ """处理单帧:推理 + 体素化 + 语义融合 + 跟踪(可选)。
Args:
frame_info: 帧信息
@@ -398,13 +685,14 @@ def _process_frame(
voxel_output_dir: 体素化输出目录
detections_dir: 检测结果图片目录
navigation_dir: 导航用点云输出目录(机器人坐标系)
+ frame_id: 帧 ID(用于跟踪)
Returns:
是否处理成功
"""
- import torch.nn.functional as F
from sharp.utils import io
from sharp.utils.gaussians import save_ply, unproject_gaussians
+ from torch.nn import functional as functional_nn
frame_path = frame_info.output_path
if frame_path is None or not frame_path.exists():
@@ -415,15 +703,16 @@ def _process_frame(
image, _, f_px = io.load_rgb(frame_path)
height, width = image.shape[:2]
- # 预处理
- internal_shape = (1536, 1536)
+ # 预处理(使用配置的内部分辨率)
+ res = self.config.internal_resolution
+ internal_shape = (res, res)
image_pt = (
torch.from_numpy(image.copy()).float().to(self._device).permute(2, 0, 1)
/ 255.0
)
disparity_factor = torch.tensor([f_px / width]).float().to(self._device)
- image_resized_pt = F.interpolate(
+ image_resized_pt = functional_nn.interpolate(
image_pt[None],
size=(internal_shape[1], internal_shape[0]),
mode="bilinear",
@@ -431,6 +720,7 @@ def _process_frame(
)
# 推理
+ assert self._predictor is not None, "Model not loaded"
gaussians_ndc = self._predictor(image_resized_pt, disparity_factor)
# 后处理
@@ -462,23 +752,44 @@ def _process_frame(
ply_path = ply_output_dir / f"{frame_path.stem}.ply"
save_ply(gaussians, f_px, (height, width), ply_path)
+ # 切片(如果启用)
+ input_ply_path = ply_path
+ if self.config.enable_slice:
+ sliced_path = self._apply_slice(ply_path, frame_path.stem)
+ if sliced_path is not None:
+ input_ply_path = sliced_path
+
# 体素化
voxel_path = voxel_output_dir / f"vox_{frame_path.stem}.ply"
+ assert self._voxelizer is not None, "Voxelizer not initialized"
self._voxelizer.process(
- ply_path,
+ input_ply_path,
voxel_path,
remove_ground=self.config.remove_ground,
transform_coords=self.config.transform_coords,
)
+ # 清理临时切片文件
+ if self.config.enable_slice and input_ply_path != ply_path:
+ with contextlib.suppress(Exception):
+ input_ply_path.unlink()
+
# 语义融合(如果启用)
if self.config.enable_semantic and self._detector is not None:
+ # 计算时间戳
+ timestamp = (
+ frame_info.timestamp
+ if frame_info.timestamp
+ else (frame_id / self.config.motion_fps)
+ )
self._apply_semantic_fusion(
frame_path,
voxel_path,
detections_dir or voxel_output_dir,
navigation_dir=navigation_dir,
focal_length=f_px, # 传递 SHARP 的精确焦距
+ frame_id=frame_id,
+ timestamp=timestamp,
)
return True
@@ -555,6 +866,12 @@ def process(
if self.config.enable_semantic:
self._load_detector()
+ # 加载跟踪器(如果启用)
+ if self.config.enable_tracking:
+ self._load_tracker()
+ if self.config.verbose:
+ logger.info("Object tracking: ENABLED")
+
self._stop_event.clear()
self._extraction_done.clear()
self._frame_queue = queue.Queue(maxsize=self.config.frame_queue_size)
@@ -582,6 +899,7 @@ def process(
voxel_dir,
detections_dir,
navigation_dir,
+ frame_id=processed_count,
)
process_time = time.time() - process_start
@@ -591,9 +909,14 @@ def process(
self.stats.total_processing_time += process_time
if self.config.verbose:
+ output_name = (
+ frame_info.output_path.name
+ if frame_info.output_path
+ else f"frame_{frame_info.index}"
+ )
logger.info(
f"Processed frame {processed_count}: "
- f"{frame_info.output_path.name} ({process_time:.2f}s)"
+ f"{output_name} ({process_time:.2f}s)"
)
if progress_callback:
@@ -613,6 +936,7 @@ def process(
if self.config.auto_unload:
self._unload_model()
self._cleanup_detector()
+ self._cleanup_tracker()
if self.config.verbose:
logger.info("=" * 50)
@@ -625,6 +949,8 @@ def process(
logger.info(f" FPS: {self.stats.frames_per_second:.2f}")
if self.config.enable_semantic:
logger.info(" Semantic detection: ENABLED")
+ if self.config.enable_tracking:
+ logger.info(" Object tracking: ENABLED")
if navigation_dir:
logger.info(f" Navigation PLY: {navigation_dir}")
logger.info("=" * 50)
@@ -636,6 +962,7 @@ def cleanup(self):
self._stop_event.set()
self._unload_model()
self._cleanup_detector()
+ self._cleanup_tracker()
self._voxelizer = None
@@ -651,6 +978,7 @@ def process_video(
semantic_model: str = "yolo11n-seg.pt",
semantic_confidence: float = 0.5,
output_navigation_ply: bool = True,
+ enable_tracking: bool = True,
) -> VideoProcessingStats:
"""便捷函数:处理视频。
@@ -666,6 +994,7 @@ def process_video(
semantic_model: YOLO 模型名称
semantic_confidence: 检测置信度阈值
output_navigation_ply: 是否输出导航用点云(机器人坐标系)
+ enable_tracking: 是否启用目标跟踪
Returns:
VideoProcessingStats: 处理统计
@@ -676,6 +1005,7 @@ def process_video(
... video_path="video.mp4",
... output_dir="output/",
... enable_semantic=True,
+ ... enable_tracking=True,
... output_navigation_ply=True,
... )
>>> print(f"Processed {stats.total_frames_processed} frames")
@@ -689,6 +1019,7 @@ def process_video(
semantic_model=semantic_model,
semantic_confidence=semantic_confidence,
output_navigation_ply=output_navigation_ply,
+ enable_tracking=enable_tracking,
)
processor = VideoPipelineProcessor(config)
diff --git a/src/aylm/tools/voxel_player.py b/src/aylm/tools/voxel_player.py
index aa1357f..a32a3a4 100644
--- a/src/aylm/tools/voxel_player.py
+++ b/src/aylm/tools/voxel_player.py
@@ -15,13 +15,10 @@
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
-from typing import TYPE_CHECKING, Any, Callable
+from typing import Any, Callable
import numpy as np
-if TYPE_CHECKING:
- import open3d as o3d
-
logger = logging.getLogger(__name__)
# 尝试导入Open3D
@@ -37,7 +34,7 @@
# 尝试导入matplotlib作为fallback
try:
import matplotlib.pyplot as plt
- from mpl_toolkits.mplot3d import Axes3D # noqa: F401
+ from mpl_toolkits.mplot3d import Axes3D # noqa: F401 - required for 3d projection
HAS_MATPLOTLIB = True
except ImportError:
@@ -304,7 +301,7 @@ def _playback_loop(self):
time.sleep(sleep_time)
# 键盘回调
- def _on_space(self, vis):
+ def _on_space(self, _vis):
"""空格键:播放/暂停。"""
if self._state == PlaybackState.PLAYING:
self._state = PlaybackState.PAUSED
@@ -316,50 +313,50 @@ def _on_space(self, vis):
logger.info("Playing")
return False
- def _on_right(self, vis):
+ def _on_right(self, _vis):
"""右箭头:下一帧。"""
if self._current_index < len(self._ply_files) - 1:
self._update_frame(self._current_index + 1)
return False
- def _on_left(self, vis):
+ def _on_left(self, _vis):
"""左箭头:上一帧。"""
if self._current_index > 0:
self._update_frame(self._current_index - 1)
return False
- def _on_up(self, vis):
+ def _on_up(self, _vis):
"""上箭头:加速。"""
self._fps = min(60.0, self._fps * 1.25)
logger.info(f"FPS: {self._fps:.1f}")
return False
- def _on_down(self, vis):
+ def _on_down(self, _vis):
"""下箭头:减速。"""
self._fps = max(1.0, self._fps / 1.25)
logger.info(f"FPS: {self._fps:.1f}")
return False
- def _on_reset(self, vis):
+ def _on_reset(self, _vis):
"""R键:重置到开头。"""
self._update_frame(0)
self._start_time = None
logger.info("Reset to beginning")
return False
- def _on_loop_toggle(self, vis):
+ def _on_loop_toggle(self, _vis):
"""L键:切换循环模式。"""
self._loop = not self._loop
logger.info(f"Loop: {'ON' if self._loop else 'OFF'}")
return False
- def _on_center(self, vis):
+ def _on_center(self, _vis):
"""C键:重置视角。"""
if self._vis is not None:
self._vis.reset_view_point(True)
return False
- def _on_quit(self, vis):
+ def _on_quit(self, _vis):
"""Q键/Esc:退出。"""
self._stop_event.set()
self._state = PlaybackState.STOPPED
@@ -497,11 +494,11 @@ def export_video(
logger.info(f"Exporting video to {output_path}")
logger.info(f" Resolution: {width}x{height}, FPS: {fps}")
- fourcc = cv2.VideoWriter_fourcc(*"mp4v")
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v") # type: ignore[attr-defined]
writer = cv2.VideoWriter(str(output_path), fourcc, fps, (width, height))
try:
- for i, ply_path in enumerate(self._ply_files):
+ for i, _ply_path in enumerate(self._ply_files):
# 使用matplotlib渲染帧
pcd = self._load_frame(i)
if pcd is None:
@@ -537,8 +534,10 @@ def export_video(
ax.set_title(f"Frame {i + 1}/{len(self._ply_files)}")
fig.canvas.draw()
- img = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
- img = img.reshape(fig.canvas.get_width_height()[::-1] + (3,))
+ img = np.frombuffer(
+ fig.canvas.tostring_rgb(), dtype=np.uint8 # type: ignore[attr-defined]
+ )
+ img = img.reshape((*fig.canvas.get_width_height()[::-1], 3))
img_bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
writer.write(img_bgr)
@@ -710,6 +709,7 @@ def play_sequence(
>>> play_sequence("output/voxelized", fps=15, loop=True)
"""
config = PlayerConfig(fps=fps, loop=loop)
+ player: VoxelPlayer | MatplotlibVoxelPlayer
if HAS_OPEN3D:
player = VoxelPlayer(config)
diff --git a/tests/unit/test_motion_estimator.py b/tests/unit/test_motion_estimator.py
new file mode 100644
index 0000000..8fb1810
--- /dev/null
+++ b/tests/unit/test_motion_estimator.py
@@ -0,0 +1,378 @@
+"""运动矢量估计器模块测试。"""
+
+import numpy as np
+import pytest
+
+from aylm.tools.motion_estimator import (
+ KalmanConfig,
+ MotionEstimator,
+ MotionVector,
+ TrackedObject3D,
+ create_tracked_object,
+)
+from aylm.tools.semantic_types import SemanticLabel
+
+
+class TestMotionVector:
+ """MotionVector 数据类测试。"""
+
+ def test_creation(self):
+ """测试创建 MotionVector。"""
+ velocity_cv = np.array([1.0, 0.0, 2.0], dtype=np.float32)
+ velocity_robot = np.array([2.0, -1.0, 0.0], dtype=np.float32)
+
+ mv = MotionVector(
+ velocity_cv=velocity_cv,
+ velocity_robot=velocity_robot,
+ speed=2.236,
+ heading=0.5,
+ is_stationary=False,
+ )
+
+ np.testing.assert_array_equal(mv.velocity_cv, velocity_cv)
+ np.testing.assert_array_equal(mv.velocity_robot, velocity_robot)
+ assert mv.speed == pytest.approx(2.236)
+ assert mv.heading == pytest.approx(0.5)
+ assert mv.is_stationary is False
+
+ def test_stationary_flag(self):
+ """测试静止标志。"""
+ mv = MotionVector(
+ velocity_cv=np.array([0.01, 0.01, 0.01], dtype=np.float32),
+ velocity_robot=np.array([0.01, -0.01, -0.01], dtype=np.float32),
+ speed=0.017,
+ heading=0.0,
+ is_stationary=True,
+ )
+ assert mv.is_stationary is True
+
+
+class TestKalmanConfig:
+ """KalmanConfig 数据类测试。"""
+
+ def test_default_values(self):
+ """测试默认配置值。"""
+ config = KalmanConfig()
+ assert config.process_noise == 0.1
+ assert config.measurement_noise == 0.5
+ assert config.initial_covariance == 1.0
+
+ def test_custom_values(self):
+ """测试自定义配置值。"""
+ config = KalmanConfig(
+ process_noise=0.2,
+ measurement_noise=0.3,
+ initial_covariance=2.0,
+ )
+ assert config.process_noise == 0.2
+ assert config.measurement_noise == 0.3
+ assert config.initial_covariance == 2.0
+
+
+class TestMotionEstimator:
+ """MotionEstimator 类测试。"""
+
+ def test_init_default(self):
+ """测试默认初始化。"""
+ estimator = MotionEstimator()
+ assert estimator.fps == 30.0
+ assert estimator.stationary_threshold == 0.1
+ assert len(estimator.active_tracks) == 0
+
+ def test_init_custom(self):
+ """测试自定义初始化。"""
+ config = KalmanConfig(process_noise=0.2)
+ estimator = MotionEstimator(
+ fps=60.0,
+ stationary_threshold=0.2,
+ kalman_config=config,
+ )
+ assert estimator.fps == 60.0
+ assert estimator.stationary_threshold == 0.2
+ assert estimator.config.process_noise == 0.2
+
+ def test_first_update_returns_none(self):
+ """测试首次更新返回 None(无速度信息)。"""
+ estimator = MotionEstimator()
+ position = np.array([1.0, 2.0, 3.0])
+
+ result = estimator.update(track_id=1, position_cv=position, frame_id=0)
+
+ assert result is None
+ assert 1 in estimator.active_tracks
+
+ def test_second_update_returns_motion(self):
+ """测试第二次更新返回运动矢量。"""
+ estimator = MotionEstimator(fps=30.0)
+
+ # 第一帧
+ pos1 = np.array([0.0, 0.0, 0.0])
+ estimator.update(track_id=1, position_cv=pos1, frame_id=0)
+
+ # 第二帧,移动了 1 米(在 Z 方向)
+ pos2 = np.array([0.0, 0.0, 1.0])
+ result = estimator.update(track_id=1, position_cv=pos2, frame_id=1)
+
+ assert result is not None
+ assert isinstance(result, MotionVector)
+ # 速度应该大于 0
+ assert result.speed > 0
+
+ def test_velocity_calculation(self):
+ """测试速度计算。"""
+ estimator = MotionEstimator(fps=1.0) # 1 FPS 便于计算
+
+ # 第一帧
+ pos1 = np.array([0.0, 0.0, 0.0])
+ estimator.update(track_id=1, position_cv=pos1, frame_id=0, timestamp=0.0)
+
+ # 第二帧,1 秒后移动 1 米
+ pos2 = np.array([1.0, 0.0, 0.0])
+ result = estimator.update(
+ track_id=1, position_cv=pos2, frame_id=1, timestamp=1.0
+ )
+
+ assert result is not None
+ # Kalman 滤波会平滑速度,但应该接近 1 m/s
+ assert result.speed > 0.5
+
+ def test_stationary_detection(self):
+ """测试静止检测。"""
+ estimator = MotionEstimator(fps=30.0, stationary_threshold=0.1)
+
+ # 第一帧
+ pos1 = np.array([0.0, 0.0, 5.0])
+ estimator.update(track_id=1, position_cv=pos1, frame_id=0)
+
+ # 第二帧,几乎不动
+ pos2 = np.array([0.001, 0.001, 5.001])
+ result = estimator.update(track_id=1, position_cv=pos2, frame_id=1)
+
+ assert result is not None
+ # 速度很小,应该被判定为静止
+ assert result.is_stationary is True
+
+ def test_moving_detection(self):
+ """测试运动检测。"""
+ estimator = MotionEstimator(fps=1.0, stationary_threshold=0.1)
+
+ # 第一帧
+ pos1 = np.array([0.0, 0.0, 0.0])
+ estimator.update(track_id=1, position_cv=pos1, frame_id=0, timestamp=0.0)
+
+ # 第二帧,明显移动
+ pos2 = np.array([2.0, 0.0, 0.0])
+ result = estimator.update(
+ track_id=1, position_cv=pos2, frame_id=1, timestamp=1.0
+ )
+
+ assert result is not None
+ assert result.is_stationary is False
+
+ def test_predict_future_position(self):
+ """测试位置预测。"""
+ estimator = MotionEstimator(fps=1.0)
+
+ # 建立轨迹
+ estimator.update(
+ track_id=1, position_cv=np.array([0.0, 0.0, 0.0]), frame_id=0, timestamp=0.0
+ )
+ estimator.update(
+ track_id=1, position_cv=np.array([1.0, 0.0, 0.0]), frame_id=1, timestamp=1.0
+ )
+
+ # 预测 1 秒后的位置
+ predicted = estimator.predict(track_id=1, dt=1.0)
+
+ assert predicted is not None
+ # 应该在 X 方向继续移动
+ assert predicted[0] > 1.0
+
+ def test_predict_nonexistent_track(self):
+ """测试预测不存在的轨迹。"""
+ estimator = MotionEstimator()
+ result = estimator.predict(track_id=999, dt=1.0)
+ assert result is None
+
+ def test_get_velocity(self):
+ """测试获取速度。"""
+ estimator = MotionEstimator(fps=1.0)
+
+ # 建立轨迹
+ estimator.update(
+ track_id=1, position_cv=np.array([0.0, 0.0, 0.0]), frame_id=0, timestamp=0.0
+ )
+ estimator.update(
+ track_id=1, position_cv=np.array([1.0, 0.0, 0.0]), frame_id=1, timestamp=1.0
+ )
+
+ velocity = estimator.get_velocity(track_id=1)
+
+ assert velocity is not None
+ assert len(velocity) == 3
+
+ def test_get_velocity_nonexistent_track(self):
+ """测试获取不存在轨迹的速度。"""
+ estimator = MotionEstimator()
+ result = estimator.get_velocity(track_id=999)
+ assert result is None
+
+ def test_multiple_tracks(self):
+ """测试多目标同时跟踪。"""
+ estimator = MotionEstimator()
+
+ # 跟踪两个目标
+ estimator.update(track_id=1, position_cv=np.array([0.0, 0.0, 0.0]), frame_id=0)
+ estimator.update(
+ track_id=2, position_cv=np.array([10.0, 10.0, 10.0]), frame_id=0
+ )
+
+ assert len(estimator.active_tracks) == 2
+ assert 1 in estimator.active_tracks
+ assert 2 in estimator.active_tracks
+
+ def test_remove_track(self):
+ """测试移除轨迹。"""
+ estimator = MotionEstimator()
+
+ estimator.update(track_id=1, position_cv=np.array([0.0, 0.0, 0.0]), frame_id=0)
+ assert 1 in estimator.active_tracks
+
+ result = estimator.remove_track(track_id=1)
+ assert result is True
+ assert 1 not in estimator.active_tracks
+
+ def test_remove_nonexistent_track(self):
+ """测试移除不存在的轨迹。"""
+ estimator = MotionEstimator()
+ result = estimator.remove_track(track_id=999)
+ assert result is False
+
+ def test_clear(self):
+ """测试清除所有轨迹。"""
+ estimator = MotionEstimator()
+
+ # 添加多个轨迹
+ for i in range(5):
+ estimator.update(
+ track_id=i, position_cv=np.array([float(i), 0.0, 0.0]), frame_id=0
+ )
+
+ assert len(estimator.active_tracks) == 5
+
+ estimator.clear()
+ assert len(estimator.active_tracks) == 0
+
+ def test_heading_calculation(self):
+ """测试航向角计算。"""
+ estimator = MotionEstimator(fps=1.0)
+
+ # 第一帧
+ estimator.update(
+ track_id=1, position_cv=np.array([0.0, 0.0, 0.0]), frame_id=0, timestamp=0.0
+ )
+
+ # 第二帧,沿 Z 轴正方向移动(OpenCV 坐标系)
+ # 在机器人坐标系中,这对应 X 轴正方向(前进)
+ result = estimator.update(
+ track_id=1, position_cv=np.array([0.0, 0.0, 5.0]), frame_id=1, timestamp=1.0
+ )
+
+ assert result is not None
+ # 航向角应该接近 0(正前方)
+ assert abs(result.heading) < 1.0 # 允许一定误差
+
+ def test_kalman_smoothing(self):
+ """测试 Kalman 滤波平滑效果。"""
+ estimator = MotionEstimator(fps=10.0)
+
+ # 模拟带噪声的轨迹
+ np.random.seed(42)
+ base_positions = [np.array([float(i), 0.0, 0.0]) for i in range(10)]
+ noisy_positions = [p + np.random.normal(0, 0.1, 3) for p in base_positions]
+
+ results = []
+ for i, pos in enumerate(noisy_positions):
+ result = estimator.update(track_id=1, position_cv=pos, frame_id=i)
+ if result is not None:
+ results.append(result)
+
+ # 速度应该相对稳定(Kalman 滤波平滑了噪声)
+ speeds = [r.speed for r in results]
+ speed_std = np.std(speeds)
+
+ # 标准差应该较小(平滑效果)
+ assert speed_std < 5.0 # 合理的阈值
+
+
+class TestTrackedObject3D:
+ """TrackedObject3D 数据类测试。"""
+
+ def test_creation(self):
+ """测试创建 TrackedObject3D。"""
+ obj = TrackedObject3D(
+ track_id=1,
+ center_cv=np.array([1.0, 2.0, 3.0], dtype=np.float32),
+ center_robot=np.array([3.0, -1.0, -2.0], dtype=np.float32),
+ dimensions=np.array([0.5, 1.7, 0.4], dtype=np.float32),
+ motion=None,
+ semantic_label=SemanticLabel.PERSON,
+ confidence=0.95,
+ frame_id=10,
+ timestamp=0.333,
+ )
+
+ assert obj.track_id == 1
+ assert obj.semantic_label == SemanticLabel.PERSON
+ assert obj.confidence == 0.95
+ assert obj.frame_id == 10
+ assert obj.timestamp == pytest.approx(0.333)
+ assert obj.motion is None
+
+
+class TestCreateTrackedObject:
+ """create_tracked_object 辅助函数测试。"""
+
+ def test_basic_creation(self):
+ """测试基本创建。"""
+ obj = create_tracked_object(
+ track_id=1,
+ center_cv=np.array([0.0, 0.0, 5.0]),
+ dimensions=np.array([0.6, 1.7, 0.4]),
+ semantic_label=SemanticLabel.PERSON,
+ confidence=0.9,
+ frame_id=0,
+ timestamp=0.0,
+ )
+
+ assert obj.track_id == 1
+ assert obj.semantic_label == SemanticLabel.PERSON
+ assert obj.confidence == 0.9
+ # 验证坐标系转换
+ assert obj.center_cv is not None
+ assert obj.center_robot is not None
+
+ def test_with_motion(self):
+ """测试带运动信息的创建。"""
+ motion = MotionVector(
+ velocity_cv=np.array([0.0, 0.0, 1.0], dtype=np.float32),
+ velocity_robot=np.array([1.0, 0.0, 0.0], dtype=np.float32),
+ speed=1.0,
+ heading=0.0,
+ is_stationary=False,
+ )
+
+ obj = create_tracked_object(
+ track_id=1,
+ center_cv=np.array([0.0, 0.0, 5.0]),
+ dimensions=np.array([0.6, 1.7, 0.4]),
+ semantic_label=SemanticLabel.VEHICLE,
+ confidence=0.85,
+ frame_id=10,
+ timestamp=0.333,
+ motion=motion,
+ )
+
+ assert obj.motion is not None
+ assert obj.motion.speed == 1.0
+ assert obj.motion.is_stationary is False
diff --git a/tests/unit/test_object_tracker.py b/tests/unit/test_object_tracker.py
new file mode 100644
index 0000000..5aca646
--- /dev/null
+++ b/tests/unit/test_object_tracker.py
@@ -0,0 +1,311 @@
+"""多目标跟踪器模块测试。"""
+
+import numpy as np
+import pytest
+
+from aylm.tools.object_tracker import (
+ MultiObjectTracker,
+ TrackedObject,
+ TrackerConfig,
+ _compute_iou,
+)
+from aylm.tools.semantic_types import Detection2D, SemanticLabel
+
+
+class TestComputeIoU:
+ """IoU 计算测试。"""
+
+ def test_identical_boxes(self):
+ """测试完全重叠的边界框。"""
+ bbox = np.array([0, 0, 100, 100], dtype=np.float32)
+ iou = _compute_iou(bbox, bbox)
+ assert iou == pytest.approx(1.0)
+
+ def test_no_overlap(self):
+ """测试无重叠的边界框。"""
+ bbox1 = np.array([0, 0, 50, 50], dtype=np.float32)
+ bbox2 = np.array([100, 100, 150, 150], dtype=np.float32)
+ iou = _compute_iou(bbox1, bbox2)
+ assert iou == 0.0
+
+ def test_partial_overlap(self):
+ """测试部分重叠的边界框。"""
+ bbox1 = np.array([0, 0, 100, 100], dtype=np.float32)
+ bbox2 = np.array([50, 50, 150, 150], dtype=np.float32)
+ # 交集: 50x50 = 2500
+ # 并集: 10000 + 10000 - 2500 = 17500
+ # IoU = 2500 / 17500 ≈ 0.143
+ iou = _compute_iou(bbox1, bbox2)
+ assert iou == pytest.approx(2500 / 17500, rel=0.01)
+
+ def test_one_inside_other(self):
+ """测试一个边界框完全在另一个内部。"""
+ bbox1 = np.array([0, 0, 100, 100], dtype=np.float32)
+ bbox2 = np.array([25, 25, 75, 75], dtype=np.float32)
+ # 交集: 50x50 = 2500
+ # 并集: 10000 (大框面积)
+ # IoU = 2500 / 10000 = 0.25
+ iou = _compute_iou(bbox1, bbox2)
+ assert iou == pytest.approx(0.25, rel=0.01)
+
+
+class TestTrackedObject:
+ """TrackedObject 数据类测试。"""
+
+ def test_creation(self):
+ """测试创建 TrackedObject。"""
+ bbox = np.array([10, 20, 110, 120], dtype=np.float32)
+ obj = TrackedObject(
+ track_id=1,
+ bbox=bbox,
+ class_id=0,
+ confidence=0.9,
+ age=5,
+ hits=3,
+ time_since_update=0,
+ )
+ assert obj.track_id == 1
+ assert obj.class_id == 0
+ assert obj.confidence == 0.9
+ assert obj.age == 5
+ assert obj.hits == 3
+ np.testing.assert_array_equal(obj.bbox, bbox)
+
+ def test_bbox_type_conversion(self):
+ """测试 bbox 类型自动转换。"""
+ # 使用列表创建
+ obj = TrackedObject(
+ track_id=1,
+ bbox=[10, 20, 110, 120], # type: ignore
+ class_id=0,
+ confidence=0.9,
+ )
+ assert obj.bbox.dtype == np.float32
+ assert isinstance(obj.bbox, np.ndarray)
+
+
+class TestMultiObjectTracker:
+ """MultiObjectTracker 类测试。"""
+
+ def _create_detection(
+ self,
+ bbox: list[float],
+ class_id: int = 0,
+ confidence: float = 0.9,
+ ) -> Detection2D:
+ """创建测试用检测结果。"""
+ return Detection2D(
+ bbox=np.array(bbox, dtype=np.float32),
+ mask=None,
+ class_id=class_id,
+ confidence=confidence,
+ semantic_label=SemanticLabel.PERSON,
+ )
+
+ def test_init_default_config(self):
+ """测试默认配置初始化。"""
+ tracker = MultiObjectTracker()
+ assert tracker.config.max_age == 30
+ assert tracker.config.min_hits == 3
+ assert tracker.config.iou_threshold == 0.3
+
+ def test_init_custom_config(self):
+ """测试自定义配置初始化。"""
+ config = TrackerConfig(max_age=10, min_hits=2, iou_threshold=0.5)
+ tracker = MultiObjectTracker(config=config)
+ assert tracker.config.max_age == 10
+ assert tracker.config.min_hits == 2
+ assert tracker.config.iou_threshold == 0.5
+
+ def test_init_with_params(self):
+ """测试使用参数初始化。"""
+ tracker = MultiObjectTracker(max_age=20, min_hits=5, iou_threshold=0.4)
+ assert tracker.config.max_age == 20
+ assert tracker.config.min_hits == 5
+ assert tracker.config.iou_threshold == 0.4
+
+ def test_first_frame_creates_tracks(self):
+ """测试第一帧创建轨迹。"""
+ tracker = MultiObjectTracker(min_hits=1)
+ detections = [
+ self._create_detection([0, 0, 50, 50]),
+ self._create_detection([100, 100, 150, 150]),
+ ]
+
+ tracked = tracker.update(detections, frame_id=0)
+
+ # min_hits=1,第一帧就应该返回确认轨迹
+ assert tracker.track_count == 2
+ assert len(tracked) == 2
+
+ def test_track_id_assignment(self):
+ """测试 track_id 分配。"""
+ tracker = MultiObjectTracker(min_hits=1)
+ detections = [
+ self._create_detection([0, 0, 50, 50]),
+ self._create_detection([100, 100, 150, 150]),
+ ]
+
+ tracked = tracker.update(detections, frame_id=0)
+
+ # 验证 track_id 是唯一的
+ track_ids = [t.track_id for t in tracked]
+ assert len(track_ids) == len(set(track_ids))
+ assert 1 in track_ids
+ assert 2 in track_ids
+
+ def test_track_association(self):
+ """测试跨帧轨迹关联。"""
+ tracker = MultiObjectTracker(min_hits=1, iou_threshold=0.3)
+
+ # 第一帧
+ det1 = [self._create_detection([0, 0, 50, 50])]
+ tracked1 = tracker.update(det1, frame_id=0)
+ track_id = tracked1[0].track_id
+
+ # 第二帧,目标稍微移动
+ det2 = [self._create_detection([5, 5, 55, 55])]
+ tracked2 = tracker.update(det2, frame_id=1)
+
+ # 应该保持相同的 track_id
+ assert len(tracked2) == 1
+ assert tracked2[0].track_id == track_id
+
+ def test_new_track_creation(self):
+ """测试新目标出现时创建新轨迹。"""
+ tracker = MultiObjectTracker(min_hits=1)
+
+ # 第一帧:一个目标
+ det1 = [self._create_detection([0, 0, 50, 50])]
+ tracker.update(det1, frame_id=0)
+
+ # 第二帧:两个目标(原有 + 新增)
+ det2 = [
+ self._create_detection([0, 0, 50, 50]),
+ self._create_detection([200, 200, 250, 250]),
+ ]
+ tracked = tracker.update(det2, frame_id=1)
+
+ assert tracker.track_count == 2
+ assert len(tracked) == 2
+
+ def test_track_deletion_after_max_age(self):
+ """测试超过 max_age 后删除轨迹。"""
+ tracker = MultiObjectTracker(max_age=3, min_hits=1)
+
+ # 第一帧:创建轨迹
+ det1 = [self._create_detection([0, 0, 50, 50])]
+ tracker.update(det1, frame_id=0)
+ assert tracker.track_count == 1
+
+ # 后续帧:无检测
+ for i in range(1, 5):
+ tracker.update([], frame_id=i)
+
+ # 轨迹应该被删除
+ assert tracker.track_count == 0
+
+ def test_min_hits_confirmation(self):
+ """测试 min_hits 确认机制。"""
+ tracker = MultiObjectTracker(min_hits=3)
+
+ det = [self._create_detection([0, 0, 50, 50])]
+
+ # 第一帧:未确认
+ tracked1 = tracker.update(det, frame_id=0)
+ assert len(tracked1) == 0
+
+ # 第二帧:未确认
+ tracked2 = tracker.update(det, frame_id=1)
+ assert len(tracked2) == 0
+
+ # 第三帧:确认
+ tracked3 = tracker.update(det, frame_id=2)
+ assert len(tracked3) == 1
+
+ def test_reset(self):
+ """测试重置跟踪器。"""
+ tracker = MultiObjectTracker(min_hits=1)
+
+ det = [self._create_detection([0, 0, 50, 50])]
+ tracker.update(det, frame_id=0)
+ assert tracker.track_count == 1
+
+ tracker.reset()
+ assert tracker.track_count == 0
+ assert tracker.confirmed_track_count == 0
+
+ def test_get_all_tracks(self):
+ """测试获取所有轨迹(包括未确认的)。"""
+ tracker = MultiObjectTracker(min_hits=3)
+
+ det = [self._create_detection([0, 0, 50, 50])]
+ tracker.update(det, frame_id=0)
+
+ # 未确认,但应该在 all_tracks 中
+ all_tracks = tracker.get_all_tracks()
+ assert len(all_tracks) == 1
+
+ def test_empty_detections(self):
+ """测试空检测列表。"""
+ tracker = MultiObjectTracker(min_hits=1)
+
+ # 空检测不应该崩溃
+ tracked = tracker.update([], frame_id=0)
+ assert len(tracked) == 0
+ assert tracker.track_count == 0
+
+ def test_track_hits_increment(self):
+ """测试 hits 计数递增。"""
+ tracker = MultiObjectTracker(min_hits=1)
+
+ det = [self._create_detection([0, 0, 50, 50])]
+
+ # 连续更新
+ for i in range(5):
+ tracked = tracker.update(det, frame_id=i)
+
+ # hits 应该是 5
+ assert len(tracked) == 1
+ assert tracked[0].hits == 5
+
+ def test_time_since_update(self):
+ """测试 time_since_update 字段。"""
+ tracker = MultiObjectTracker(min_hits=1, max_age=10)
+
+ det = [self._create_detection([0, 0, 50, 50])]
+
+ # 第一帧
+ tracker.update(det, frame_id=0)
+
+ # 后续帧无检测
+ tracker.update([], frame_id=1)
+ tracker.update([], frame_id=2)
+
+ # 获取所有轨迹(包括未匹配的)
+ all_tracks = tracker.get_all_tracks()
+ assert len(all_tracks) == 1
+ assert all_tracks[0].time_since_update == 2
+
+ def test_class_id_preserved(self):
+ """测试 class_id 保持不变。"""
+ tracker = MultiObjectTracker(min_hits=1)
+
+ det = [self._create_detection([0, 0, 50, 50], class_id=2)]
+ tracked = tracker.update(det, frame_id=0)
+
+ assert tracked[0].class_id == 2
+
+ def test_confidence_updated(self):
+ """测试置信度更新。"""
+ tracker = MultiObjectTracker(min_hits=1)
+
+ # 第一帧
+ det1 = [self._create_detection([0, 0, 50, 50], confidence=0.8)]
+ tracker.update(det1, frame_id=0)
+
+ # 第二帧,置信度变化
+ det2 = [self._create_detection([0, 0, 50, 50], confidence=0.95)]
+ tracked = tracker.update(det2, frame_id=1)
+
+ assert tracked[0].confidence == 0.95