+
+## Abstract
+
+We present Depth Anything 3 (DA3), a model that predicts spatially consistent geometry from an arbitrary number of visual inputs, with or without known camera poses. In pursuit of minimal modeling, DA3 yields two key insights: a single plain transformer (e.g., vanilla DINO encoder) is sufficient as a backbone without architectural specialization, and a singular depth-ray prediction target obviates the need for complex multi-task learning. Through our teacher-student training paradigm, the model achieves a level of detail and generalization on par with Depth Anything 2 (DA2). We establish a new visual geometry benchmark covering camera pose estimation, any-view geometry and visual rendering. On this benchmark, DA3 sets a new state-of-the-art across all tasks, surpassing prior SOTA VGGT by an average of 44.3% in camera pose accuracy and 25.1% in geometric accuracy. Moreover, it outperforms DA2 in monocular depth estimation. All models are trained exclusively on public academic datasets.
+
+## Model Description
+
+DA3 Base model for multi-view depth estimation and camera pose estimation. Compact foundation model with unified depth-ray representation.
+
+| Property | Value |
+|----------|-------|
+| **Model Series** | Any-view Model |
+| **Parameters** | 0.12B |
+| **License** | Apache 2.0 |
+
+
+
+## Capabilities
+
+- ✅ Relative Depth
+- ✅ Pose Estimation
+- ✅ Pose Conditioning
+
+## Quick Start
+
+### Installation
+
+```bash
+git clone https://github.com/ByteDance-Seed/depth-anything-3
+cd depth-anything-3
+pip install -e .
+```
+
+### Basic Example
+
+```python
+import torch
+from depth_anything_3.api import DepthAnything3
+
+# Load model from Hugging Face Hub
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+model = DepthAnything3.from_pretrained("depth-anything/da3-base")
+model = model.to(device=device)
+
+# Run inference on images
+images = ["image1.jpg", "image2.jpg"] # List of image paths, PIL Images, or numpy arrays
+prediction = model.inference(
+ images,
+ export_dir="output",
+ export_format="glb" # Options: glb, npz, ply, mini_npz, gs_ply, gs_video
+)
+
+# Access results
+print(prediction.depth.shape) # Depth maps: [N, H, W] float32
+print(prediction.conf.shape) # Confidence maps: [N, H, W] float32
+print(prediction.extrinsics.shape) # Camera poses (w2c): [N, 3, 4] float32
+print(prediction.intrinsics.shape) # Camera intrinsics: [N, 3, 3] float32
+```
+
+### Command Line Interface
+
+```bash
+# Process images with auto mode
+da3 auto path/to/images \
+ --export-format glb \
+ --export-dir output \
+ --model-dir depth-anything/da3-base
+
+# Use backend for faster repeated inference
+da3 backend --model-dir depth-anything/da3-base
+da3 auto path/to/images --export-format glb --use-backend
+```
+
+## Model Details
+
+- **Developed by:** ByteDance Seed Team
+- **Model Type:** Vision Transformer for Visual Geometry
+- **Architecture:** Plain transformer with unified depth-ray representation
+- **Training Data:** Public academic datasets only
+
+### Key Insights
+
+💎 A **single plain transformer** (e.g., vanilla DINO encoder) is sufficient as a backbone without architectural specialization. # noqa: E501
+
+✨ A singular **depth-ray representation** obviates the need for complex multi-task learning.
+
+## Performance
+
+🏆 Depth Anything 3 significantly outperforms:
+- **Depth Anything 2** for monocular depth estimation
+- **VGGT** for multi-view depth estimation and pose estimation
+
+For detailed benchmarks, please refer to our [paper](https://arxiv.org/abs/2511.10647). # noqa: E501
+
+## Limitations
+
+- The model is trained on academic datasets and may have limitations on certain domain-specific images # noqa: E501
+- Performance may vary depending on image quality, lighting conditions, and scene complexity
+
+
+## Citation
+
+If you find Depth Anything 3 useful in your research or projects, please cite:
+
+```bibtex
+@article{depthanything3,
+ title={Depth Anything 3: Recovering the visual space from any views},
+ author={Haotong Lin and Sili Chen and Jun Hao Liew and Donny Y. Chen and Zhenyu Li and Guang Shi and Jiashi Feng and Bingyi Kang}, # noqa: E501
+ journal={arXiv preprint arXiv:2511.10647},
+ year={2025}
+}
+```
+
+## Links
+
+- 🏠 [Project Page](https://depth-anything-3.github.io)
+- 📄 [Paper](https://arxiv.org/abs/2511.10647)
+- 💻 [GitHub Repository](https://github.com/ByteDance-Seed/depth-anything-3)
+- 🤗 [Hugging Face Demo](https://huggingface.co/spaces/depth-anything/Depth-Anything-3)
+- 📚 [Documentation](https://github.com/ByteDance-Seed/depth-anything-3#-useful-documentation)
+
+## Authors
+
+[Haotong Lin](https://haotongl.github.io/) · [Sili Chen](https://github.com/SiliChen321) · [Junhao Liew](https://liewjunhao.github.io/) · [Donny Y. Chen](https://donydchen.github.io) · [Zhenyu Li](https://zhyever.github.io/) · [Guang Shi](https://scholar.google.com/citations?user=MjXxWbUAAAAJ&hl=en) · [Jiashi Feng](https://scholar.google.com.sg/citations?user=Q8iay0gAAAAJ&hl=en) · [Bingyi Kang](https://bingykang.github.io/) # noqa: E501
\ No newline at end of file
diff --git a/cameras/config.json b/cameras/config.json
new file mode 100644
index 0000000000000000000000000000000000000000..4fbbc20883d49b5c1d06683753c8ad1a8d4a97e5
--- /dev/null
+++ b/cameras/config.json
@@ -0,0 +1,60 @@
+{
+ "model_name": "da3-base",
+ "config": {
+ "__object__": {
+ "path": "depth_anything_3.model.da3",
+ "name": "DepthAnything3Net",
+ "args": "as_params"
+ },
+ "net": {
+ "__object__": {
+ "path": "depth_anything_3.model.dinov2.dinov2",
+ "name": "DinoV2",
+ "args": "as_params"
+ },
+ "name": "vitb",
+ "out_layers": [
+ 5,
+ 7,
+ 9,
+ 11
+ ],
+ "alt_start": 4,
+ "qknorm_start": 4,
+ "rope_start": 4,
+ "cat_token": true
+ },
+ "head": {
+ "__object__": {
+ "path": "depth_anything_3.model.dualdpt",
+ "name": "DualDPT",
+ "args": "as_params"
+ },
+ "dim_in": 1536,
+ "output_dim": 2,
+ "features": 128,
+ "out_channels": [
+ 96,
+ 192,
+ 384,
+ 768
+ ]
+ },
+ "cam_enc": {
+ "__object__": {
+ "path": "depth_anything_3.model.cam_enc",
+ "name": "CameraEnc",
+ "args": "as_params"
+ },
+ "dim_out": 768
+ },
+ "cam_dec": {
+ "__object__": {
+ "path": "depth_anything_3.model.cam_dec",
+ "name": "CameraDec",
+ "args": "as_params"
+ },
+ "dim_in": 1536
+ }
+ }
+}
\ No newline at end of file
diff --git a/cameras/deps/addict-2.4.0.dist-info/INSTALLER b/cameras/deps/addict-2.4.0.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/cameras/deps/addict-2.4.0.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/cameras/deps/addict-2.4.0.dist-info/LICENSE b/cameras/deps/addict-2.4.0.dist-info/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..c9a0fae0f709d76c3246a876b03671cd866be162
--- /dev/null
+++ b/cameras/deps/addict-2.4.0.dist-info/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Mats Julian Olsen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/cameras/deps/addict-2.4.0.dist-info/METADATA b/cameras/deps/addict-2.4.0.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..864b661e77934b1dde26365b034e3bb5505cc1ee
--- /dev/null
+++ b/cameras/deps/addict-2.4.0.dist-info/METADATA
@@ -0,0 +1,23 @@
+Metadata-Version: 2.1
+Name: addict
+Version: 2.4.0
+Summary: Addict is a dictionary whose items can be set using both attribute and item syntax.
+Home-page: https://github.com/mewwts/addict
+Author: Mats Julian Olsen
+Author-email: mats@plysjbyen.net
+License: UNKNOWN
+Platform: UNKNOWN
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Intended Audience :: Developers
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+
+Addict is a module that exposes a dictionary subclass that allows items to be set like attributes. Values are gettable and settable using both attribute and item syntax. For more info check out the README at 'github.com/mewwts/addict'.
+
+
diff --git a/cameras/deps/addict-2.4.0.dist-info/RECORD b/cameras/deps/addict-2.4.0.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..5d72a633b641faf5996cbf5034960d4b12ac5d59
--- /dev/null
+++ b/cameras/deps/addict-2.4.0.dist-info/RECORD
@@ -0,0 +1,9 @@
+addict-2.4.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+addict-2.4.0.dist-info/LICENSE,sha256=ykiNM8US0LImFCCQr5DomuJmqQGik_if1kLf7JMeIsE,1085
+addict-2.4.0.dist-info/METADATA,sha256=ZjIJZ5lvhfohHxIWp79fkh0TtBsN5duX84na3bxU6vQ,1028
+addict-2.4.0.dist-info/RECORD,,
+addict-2.4.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+addict-2.4.0.dist-info/WHEEL,sha256=EVRjI69F5qVjm_YgqcTXPnTAv3BfSUr0WVAHuSP3Xoo,92
+addict-2.4.0.dist-info/top_level.txt,sha256=GGfQKsz1N-bSRYzNpyp8GY8uAEqh3SWaXRbAnBUKjHA,7
+addict/__init__.py,sha256=xmlNiYYVoAD3_FfmgfOM2cc3wI17GSgsMBODbFRs_mY,233
+addict/addict.py,sha256=ScOfclaAM7ugyZoXOW2HHL9gCXt_XPe-i5bTLiY8ufU,4901
diff --git a/cameras/deps/addict-2.4.0.dist-info/REQUESTED b/cameras/deps/addict-2.4.0.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/cameras/deps/addict-2.4.0.dist-info/WHEEL b/cameras/deps/addict-2.4.0.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..83ff02e961fce5ad7befce746ff02635e1616315
--- /dev/null
+++ b/cameras/deps/addict-2.4.0.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.35.1)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/cameras/deps/addict-2.4.0.dist-info/top_level.txt b/cameras/deps/addict-2.4.0.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b765b5f4af811ab630ffa864563a666850c266fc
--- /dev/null
+++ b/cameras/deps/addict-2.4.0.dist-info/top_level.txt
@@ -0,0 +1 @@
+addict
diff --git a/cameras/deps/addict/__init__.py b/cameras/deps/addict/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0af039c6298a3889a8e8b2e0329fe2313c120d0
--- /dev/null
+++ b/cameras/deps/addict/__init__.py
@@ -0,0 +1,10 @@
+from .addict import Dict
+from .addict import Dict as Addict
+
+
+__title__ = 'addict'
+__version__ = '2.4.0'
+__author__ = 'Mats Julian Olsen'
+__license__ = 'MIT'
+__copyright__ = 'Copyright 2014-2020 Mats Julian Olsen'
+__all__ = ['Dict']
diff --git a/cameras/deps/addict/addict.py b/cameras/deps/addict/addict.py
new file mode 100644
index 0000000000000000000000000000000000000000..55e02d1d17596c77a6f3642ba02eeb30971048bd
--- /dev/null
+++ b/cameras/deps/addict/addict.py
@@ -0,0 +1,159 @@
+import copy
+
+
+class Dict(dict):
+
+ def __init__(__self, *args, **kwargs):
+ object.__setattr__(__self, '__parent', kwargs.pop('__parent', None))
+ object.__setattr__(__self, '__key', kwargs.pop('__key', None))
+ object.__setattr__(__self, '__frozen', False)
+ for arg in args:
+ if not arg:
+ continue
+ elif isinstance(arg, dict):
+ for key, val in arg.items():
+ __self[key] = __self._hook(val)
+ elif isinstance(arg, tuple) and (not isinstance(arg[0], tuple)):
+ __self[arg[0]] = __self._hook(arg[1])
+ else:
+ for key, val in iter(arg):
+ __self[key] = __self._hook(val)
+
+ for key, val in kwargs.items():
+ __self[key] = __self._hook(val)
+
+ def __setattr__(self, name, value):
+ if hasattr(self.__class__, name):
+ raise AttributeError("'Dict' object attribute "
+ "'{0}' is read-only".format(name))
+ else:
+ self[name] = value
+
+ def __setitem__(self, name, value):
+ isFrozen = (hasattr(self, '__frozen') and
+ object.__getattribute__(self, '__frozen'))
+ if isFrozen and name not in super(Dict, self).keys():
+ raise KeyError(name)
+ super(Dict, self).__setitem__(name, value)
+ try:
+ p = object.__getattribute__(self, '__parent')
+ key = object.__getattribute__(self, '__key')
+ except AttributeError:
+ p = None
+ key = None
+ if p is not None:
+ p[key] = self
+ object.__delattr__(self, '__parent')
+ object.__delattr__(self, '__key')
+
+ def __add__(self, other):
+ if not self.keys():
+ return other
+ else:
+ self_type = type(self).__name__
+ other_type = type(other).__name__
+ msg = "unsupported operand type(s) for +: '{}' and '{}'"
+ raise TypeError(msg.format(self_type, other_type))
+
+ @classmethod
+ def _hook(cls, item):
+ if isinstance(item, dict):
+ return cls(item)
+ elif isinstance(item, (list, tuple)):
+ return type(item)(cls._hook(elem) for elem in item)
+ return item
+
+ def __getattr__(self, item):
+ return self.__getitem__(item)
+
+ def __missing__(self, name):
+ if object.__getattribute__(self, '__frozen'):
+ raise KeyError(name)
+ return self.__class__(__parent=self, __key=name)
+
+ def __delattr__(self, name):
+ del self[name]
+
+ def to_dict(self):
+ base = {}
+ for key, value in self.items():
+ if isinstance(value, type(self)):
+ base[key] = value.to_dict()
+ elif isinstance(value, (list, tuple)):
+ base[key] = type(value)(
+ item.to_dict() if isinstance(item, type(self)) else
+ item for item in value)
+ else:
+ base[key] = value
+ return base
+
+ def copy(self):
+ return copy.copy(self)
+
+ def deepcopy(self):
+ return copy.deepcopy(self)
+
+ def __deepcopy__(self, memo):
+ other = self.__class__()
+ memo[id(self)] = other
+ for key, value in self.items():
+ other[copy.deepcopy(key, memo)] = copy.deepcopy(value, memo)
+ return other
+
+ def update(self, *args, **kwargs):
+ other = {}
+ if args:
+ if len(args) > 1:
+ raise TypeError()
+ other.update(args[0])
+ other.update(kwargs)
+ for k, v in other.items():
+ if ((k not in self) or
+ (not isinstance(self[k], dict)) or
+ (not isinstance(v, dict))):
+ self[k] = v
+ else:
+ self[k].update(v)
+
+ def __getnewargs__(self):
+ return tuple(self.items())
+
+ def __getstate__(self):
+ return self
+
+ def __setstate__(self, state):
+ self.update(state)
+
+ def __or__(self, other):
+ if not isinstance(other, (Dict, dict)):
+ return NotImplemented
+ new = Dict(self)
+ new.update(other)
+ return new
+
+ def __ror__(self, other):
+ if not isinstance(other, (Dict, dict)):
+ return NotImplemented
+ new = Dict(other)
+ new.update(self)
+ return new
+
+ def __ior__(self, other):
+ self.update(other)
+ return self
+
+ def setdefault(self, key, default=None):
+ if key in self:
+ return self[key]
+ else:
+ self[key] = default
+ return default
+
+ def freeze(self, shouldFreeze=True):
+ object.__setattr__(self, '__frozen', shouldFreeze)
+ for key, val in self.items():
+ if isinstance(val, Dict):
+ val.freeze(shouldFreeze)
+
+ def unfreeze(self):
+ self.freeze(False)
diff --git a/cameras/deps/antlr4/BufferedTokenStream.py b/cameras/deps/antlr4/BufferedTokenStream.py
new file mode 100644
index 0000000000000000000000000000000000000000..aaa58c63dc02b8b0bf43398b751f1d14fef89ad9
--- /dev/null
+++ b/cameras/deps/antlr4/BufferedTokenStream.py
@@ -0,0 +1,302 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+
+# This implementation of {@link TokenStream} loads tokens from a
+# {@link TokenSource} on-demand, and places the tokens in a buffer to provide
+# access to any previous token by index.
+#
+#
+# This token stream ignores the value of {@link Token#getChannel}. If your
+# parser requires the token stream filter tokens to only those on a particular
+# channel, such as {@link Token#DEFAULT_CHANNEL} or
+# {@link Token#HIDDEN_CHANNEL}, use a filtering token stream such a
+# {@link CommonTokenStream}.
+from io import StringIO
+from antlr4.Token import Token
+from antlr4.error.Errors import IllegalStateException
+
+# need forward declaration
+Lexer = None
+
+# this is just to keep meaningful parameter types to Parser
+class TokenStream(object):
+
+ pass
+
+
+class BufferedTokenStream(TokenStream):
+ __slots__ = ('tokenSource', 'tokens', 'index', 'fetchedEOF')
+
+ def __init__(self, tokenSource:Lexer):
+ # The {@link TokenSource} from which tokens for this stream are fetched.
+ self.tokenSource = tokenSource
+
+ # A collection of all tokens fetched from the token source. The list is
+ # considered a complete view of the input once {@link #fetchedEOF} is set
+ # to {@code true}.
+ self.tokens = []
+
+ # The index into {@link #tokens} of the current token (next token to
+ # {@link #consume}). {@link #tokens}{@code [}{@link #p}{@code ]} should be
+ # {@link #LT LT(1)}.
+ #
+ #
This field is set to -1 when the stream is first constructed or when
+ # {@link #setTokenSource} is called, indicating that the first token has
+ # not yet been fetched from the token source. For additional information,
+ # see the documentation of {@link IntStream} for a description of
+ # Initializing Methods.
+ self.index = -1
+
+ # Indicates whether the {@link Token#EOF} token has been fetched from
+ # {@link #tokenSource} and added to {@link #tokens}. This field improves
+ # performance for the following cases:
+ #
+ #
+ #
{@link #consume}: The lookahead check in {@link #consume} to prevent
+ # consuming the EOF symbol is optimized by checking the values of
+ # {@link #fetchedEOF} and {@link #p} instead of calling {@link #LA}.
+ #
{@link #fetch}: The check to prevent adding multiple EOF symbols into
+ # {@link #tokens} is trivial with this field.
+ #
+ self.fetchedEOF = False
+
+ def mark(self):
+ return 0
+
+ def release(self, marker:int):
+ # no resources to release
+ pass
+
+ def reset(self):
+ self.seek(0)
+
+ def seek(self, index:int):
+ self.lazyInit()
+ self.index = self.adjustSeekIndex(index)
+
+ def get(self, index:int):
+ self.lazyInit()
+ return self.tokens[index]
+
+ def consume(self):
+ skipEofCheck = False
+ if self.index >= 0:
+ if self.fetchedEOF:
+ # the last token in tokens is EOF. skip check if p indexes any
+ # fetched token except the last.
+ skipEofCheck = self.index < len(self.tokens) - 1
+ else:
+ # no EOF token in tokens. skip check if p indexes a fetched token.
+ skipEofCheck = self.index < len(self.tokens)
+ else:
+ # not yet initialized
+ skipEofCheck = False
+
+ if not skipEofCheck and self.LA(1) == Token.EOF:
+ raise IllegalStateException("cannot consume EOF")
+
+ if self.sync(self.index + 1):
+ self.index = self.adjustSeekIndex(self.index + 1)
+
+ # Make sure index {@code i} in tokens has a token.
+ #
+ # @return {@code true} if a token is located at index {@code i}, otherwise
+ # {@code false}.
+ # @see #get(int i)
+ #/
+ def sync(self, i:int):
+ n = i - len(self.tokens) + 1 # how many more elements we need?
+ if n > 0 :
+ fetched = self.fetch(n)
+ return fetched >= n
+ return True
+
+ # Add {@code n} elements to buffer.
+ #
+ # @return The actual number of elements added to the buffer.
+ #/
+ def fetch(self, n:int):
+ if self.fetchedEOF:
+ return 0
+ for i in range(0, n):
+ t = self.tokenSource.nextToken()
+ t.tokenIndex = len(self.tokens)
+ self.tokens.append(t)
+ if t.type==Token.EOF:
+ self.fetchedEOF = True
+ return i + 1
+ return n
+
+
+ # Get all tokens from start..stop inclusively#/
+ def getTokens(self, start:int, stop:int, types:set=None):
+ if start<0 or stop<0:
+ return None
+ self.lazyInit()
+ subset = []
+ if stop >= len(self.tokens):
+ stop = len(self.tokens)-1
+ for i in range(start, stop):
+ t = self.tokens[i]
+ if t.type==Token.EOF:
+ break
+ if types is None or t.type in types:
+ subset.append(t)
+ return subset
+
+ def LA(self, i:int):
+ return self.LT(i).type
+
+ def LB(self, k:int):
+ if (self.index-k) < 0:
+ return None
+ return self.tokens[self.index-k]
+
+ def LT(self, k:int):
+ self.lazyInit()
+ if k==0:
+ return None
+ if k < 0:
+ return self.LB(-k)
+ i = self.index + k - 1
+ self.sync(i)
+ if i >= len(self.tokens): # return EOF token
+ # EOF must be last token
+ return self.tokens[len(self.tokens)-1]
+ return self.tokens[i]
+
+ # Allowed derived classes to modify the behavior of operations which change
+ # the current stream position by adjusting the target token index of a seek
+ # operation. The default implementation simply returns {@code i}. If an
+ # exception is thrown in this method, the current stream index should not be
+ # changed.
+ #
+ #
For example, {@link CommonTokenStream} overrides this method to ensure that
+ # the seek target is always an on-channel token.
+ #
+ # @param i The target token index.
+ # @return The adjusted target token index.
+
+ def adjustSeekIndex(self, i:int):
+ return i
+
+ def lazyInit(self):
+ if self.index == -1:
+ self.setup()
+
+ def setup(self):
+ self.sync(0)
+ self.index = self.adjustSeekIndex(0)
+
+ # Reset this token stream by setting its token source.#/
+ def setTokenSource(self, tokenSource:Lexer):
+ self.tokenSource = tokenSource
+ self.tokens = []
+ self.index = -1
+ self.fetchedEOF = False
+
+
+ # Given a starting index, return the index of the next token on channel.
+ # Return i if tokens[i] is on channel. Return the index of the EOF token
+ # if there are no tokens on channel between i and EOF.
+ #/
+ def nextTokenOnChannel(self, i:int, channel:int):
+ self.sync(i)
+ if i>=len(self.tokens):
+ return len(self.tokens) - 1
+ token = self.tokens[i]
+ while token.channel!=channel:
+ if token.type==Token.EOF:
+ return i
+ i += 1
+ self.sync(i)
+ token = self.tokens[i]
+ return i
+
+ # Given a starting index, return the index of the previous token on channel.
+ # Return i if tokens[i] is on channel. Return -1 if there are no tokens
+ # on channel between i and 0.
+ def previousTokenOnChannel(self, i:int, channel:int):
+ while i>=0 and self.tokens[i].channel!=channel:
+ i -= 1
+ return i
+
+ # Collect all tokens on specified channel to the right of
+ # the current token up until we see a token on DEFAULT_TOKEN_CHANNEL or
+ # EOF. If channel is -1, find any non default channel token.
+ def getHiddenTokensToRight(self, tokenIndex:int, channel:int=-1):
+ self.lazyInit()
+ if tokenIndex<0 or tokenIndex>=len(self.tokens):
+ raise Exception(str(tokenIndex) + " not in 0.." + str(len(self.tokens)-1))
+ from antlr4.Lexer import Lexer
+ nextOnChannel = self.nextTokenOnChannel(tokenIndex + 1, Lexer.DEFAULT_TOKEN_CHANNEL)
+ from_ = tokenIndex+1
+ # if none onchannel to right, nextOnChannel=-1 so set to = last token
+ to = (len(self.tokens)-1) if nextOnChannel==-1 else nextOnChannel
+ return self.filterForChannel(from_, to, channel)
+
+
+ # Collect all tokens on specified channel to the left of
+ # the current token up until we see a token on DEFAULT_TOKEN_CHANNEL.
+ # If channel is -1, find any non default channel token.
+ def getHiddenTokensToLeft(self, tokenIndex:int, channel:int=-1):
+ self.lazyInit()
+ if tokenIndex<0 or tokenIndex>=len(self.tokens):
+ raise Exception(str(tokenIndex) + " not in 0.." + str(len(self.tokens)-1))
+ from antlr4.Lexer import Lexer
+ prevOnChannel = self.previousTokenOnChannel(tokenIndex - 1, Lexer.DEFAULT_TOKEN_CHANNEL)
+ if prevOnChannel == tokenIndex - 1:
+ return None
+ # if none on channel to left, prevOnChannel=-1 then from=0
+ from_ = prevOnChannel+1
+ to = tokenIndex-1
+ return self.filterForChannel(from_, to, channel)
+
+
+ def filterForChannel(self, left:int, right:int, channel:int):
+ hidden = []
+ for i in range(left, right+1):
+ t = self.tokens[i]
+ if channel==-1:
+ from antlr4.Lexer import Lexer
+ if t.channel!= Lexer.DEFAULT_TOKEN_CHANNEL:
+ hidden.append(t)
+ elif t.channel==channel:
+ hidden.append(t)
+ if len(hidden)==0:
+ return None
+ return hidden
+
+ def getSourceName(self):
+ return self.tokenSource.getSourceName()
+
+ # Get the text of all tokens in this buffer.#/
+ def getText(self, start:int=None, stop:int=None):
+ self.lazyInit()
+ self.fill()
+ if isinstance(start, Token):
+ start = start.tokenIndex
+ elif start is None:
+ start = 0
+ if isinstance(stop, Token):
+ stop = stop.tokenIndex
+ elif stop is None or stop >= len(self.tokens):
+ stop = len(self.tokens) - 1
+ if start < 0 or stop < 0 or stop < start:
+ return ""
+ with StringIO() as buf:
+ for i in range(start, stop+1):
+ t = self.tokens[i]
+ if t.type==Token.EOF:
+ break
+ buf.write(t.text)
+ return buf.getvalue()
+
+
+ # Get all tokens from lexer until EOF#/
+ def fill(self):
+ self.lazyInit()
+ while self.fetch(1000)==1000:
+ pass
diff --git a/cameras/deps/antlr4/CommonTokenFactory.py b/cameras/deps/antlr4/CommonTokenFactory.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d09ad78598de14f7a32d813c667e877ef1c0f6c
--- /dev/null
+++ b/cameras/deps/antlr4/CommonTokenFactory.py
@@ -0,0 +1,61 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# This default implementation of {@link TokenFactory} creates
+# {@link CommonToken} objects.
+#
+from antlr4.Token import CommonToken
+
+class TokenFactory(object):
+
+ pass
+
+class CommonTokenFactory(TokenFactory):
+ __slots__ = 'copyText'
+
+ #
+ # The default {@link CommonTokenFactory} instance.
+ #
+ #
+ # This token factory does not explicitly copy token text when constructing
+ # tokens.
+ #
+ DEFAULT = None
+
+ def __init__(self, copyText:bool=False):
+ # Indicates whether {@link CommonToken#setText} should be called after
+ # constructing tokens to explicitly set the text. This is useful for cases
+ # where the input stream might not be able to provide arbitrary substrings
+ # of text from the input after the lexer creates a token (e.g. the
+ # implementation of {@link CharStream#getText} in
+ # {@link UnbufferedCharStream} throws an
+ # {@link UnsupportedOperationException}). Explicitly setting the token text
+ # allows {@link Token#getText} to be called at any time regardless of the
+ # input stream implementation.
+ #
+ #
+ # The default value is {@code false} to avoid the performance and memory
+ # overhead of copying text for every token unless explicitly requested.
+ #
+ self.copyText = copyText
+
+ def create(self, source, type:int, text:str, channel:int, start:int, stop:int, line:int, column:int):
+ t = CommonToken(source, type, channel, start, stop)
+ t.line = line
+ t.column = column
+ if text is not None:
+ t.text = text
+ elif self.copyText and source[1] is not None:
+ t.text = source[1].getText(start,stop)
+ return t
+
+ def createThin(self, type:int, text:str):
+ t = CommonToken(type=type)
+ t.text = text
+ return t
+
+CommonTokenFactory.DEFAULT = CommonTokenFactory()
diff --git a/cameras/deps/antlr4/CommonTokenStream.py b/cameras/deps/antlr4/CommonTokenStream.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd0a984e33fe42b3499844705b698953e7f04af9
--- /dev/null
+++ b/cameras/deps/antlr4/CommonTokenStream.py
@@ -0,0 +1,87 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+#
+# This class extends {@link BufferedTokenStream} with functionality to filter
+# token streams to tokens on a particular channel (tokens where
+# {@link Token#getChannel} returns a particular value).
+#
+#
+# This token stream provides access to all tokens by index or when calling
+# methods like {@link #getText}. The channel filtering is only used for code
+# accessing tokens via the lookahead methods {@link #LA}, {@link #LT}, and
+# {@link #LB}.
+#
+#
+# By default, tokens are placed on the default channel
+# ({@link Token#DEFAULT_CHANNEL}), but may be reassigned by using the
+# {@code ->channel(HIDDEN)} lexer command, or by using an embedded action to
+# call {@link Lexer#setChannel}.
+#
+#
+#
+# Note: lexer rules which use the {@code ->skip} lexer command or call
+# {@link Lexer#skip} do not produce tokens at all, so input text matched by
+# such a rule will not be available as part of the token stream, regardless of
+# channel.
+#/
+
+from antlr4.BufferedTokenStream import BufferedTokenStream
+from antlr4.Lexer import Lexer
+from antlr4.Token import Token
+
+
+class CommonTokenStream(BufferedTokenStream):
+ __slots__ = 'channel'
+
+ def __init__(self, lexer:Lexer, channel:int=Token.DEFAULT_CHANNEL):
+ super().__init__(lexer)
+ self.channel = channel
+
+ def adjustSeekIndex(self, i:int):
+ return self.nextTokenOnChannel(i, self.channel)
+
+ def LB(self, k:int):
+ if k==0 or (self.index-k)<0:
+ return None
+ i = self.index
+ n = 1
+ # find k good tokens looking backwards
+ while n <= k:
+ # skip off-channel tokens
+ i = self.previousTokenOnChannel(i - 1, self.channel)
+ n += 1
+ if i < 0:
+ return None
+ return self.tokens[i]
+
+ def LT(self, k:int):
+ self.lazyInit()
+ if k == 0:
+ return None
+ if k < 0:
+ return self.LB(-k)
+ i = self.index
+ n = 1 # we know tokens[pos] is a good one
+ # find k good tokens
+ while n < k:
+ # skip off-channel tokens, but make sure to not look past EOF
+ if self.sync(i + 1):
+ i = self.nextTokenOnChannel(i + 1, self.channel)
+ n += 1
+ return self.tokens[i]
+
+ # Count EOF just once.#/
+ def getNumberOfOnChannelTokens(self):
+ n = 0
+ self.fill()
+ for i in range(0, len(self.tokens)):
+ t = self.tokens[i]
+ if t.channel==self.channel:
+ n += 1
+ if t.type==Token.EOF:
+ break
+ return n
diff --git a/cameras/deps/antlr4/FileStream.py b/cameras/deps/antlr4/FileStream.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c6ce9fbc02a6f42965f44d5df0a55787626d7eb
--- /dev/null
+++ b/cameras/deps/antlr4/FileStream.py
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# This is an InputStream that is loaded from a file all at once
+# when you construct the object.
+#
+
+import codecs
+from antlr4.InputStream import InputStream
+
+
+class FileStream(InputStream):
+ __slots__ = 'fileName'
+
+ def __init__(self, fileName:str, encoding:str='ascii', errors:str='strict'):
+ super().__init__(self.readDataFrom(fileName, encoding, errors))
+ self.fileName = fileName
+
+ def readDataFrom(self, fileName:str, encoding:str, errors:str='strict'):
+ # read binary to avoid line ending conversion
+ with open(fileName, 'rb') as file:
+ bytes = file.read()
+ return codecs.decode(bytes, encoding, errors)
diff --git a/cameras/deps/antlr4/InputStream.py b/cameras/deps/antlr4/InputStream.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d077d13576d2fd5f91153608f91e907092b87d5
--- /dev/null
+++ b/cameras/deps/antlr4/InputStream.py
@@ -0,0 +1,87 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+
+#
+# Vacuum all input from a string and then treat it like a buffer.
+#
+from antlr4.Token import Token
+
+
+class InputStream (object):
+ __slots__ = ('name', 'strdata', '_index', 'data', '_size')
+
+ def __init__(self, data: str):
+ self.name = ""
+ self.strdata = data
+ self._loadString()
+
+ def _loadString(self):
+ self._index = 0
+ self.data = [ord(c) for c in self.strdata]
+ self._size = len(self.data)
+
+ @property
+ def index(self):
+ return self._index
+
+ @property
+ def size(self):
+ return self._size
+
+ # Reset the stream so that it's in the same state it was
+ # when the object was created *except* the data array is not
+ # touched.
+ #
+ def reset(self):
+ self._index = 0
+
+ def consume(self):
+ if self._index >= self._size:
+ assert self.LA(1) == Token.EOF
+ raise Exception("cannot consume EOF")
+ self._index += 1
+
+ def LA(self, offset: int):
+ if offset==0:
+ return 0 # undefined
+ if offset<0:
+ offset += 1 # e.g., translate LA(-1) to use offset=0
+ pos = self._index + offset - 1
+ if pos < 0 or pos >= self._size: # invalid
+ return Token.EOF
+ return self.data[pos]
+
+ def LT(self, offset: int):
+ return self.LA(offset)
+
+ # mark/release do nothing; we have entire buffer
+ def mark(self):
+ return -1
+
+ def release(self, marker: int):
+ pass
+
+ # consume() ahead until p==_index; can't just set p=_index as we must
+ # update line and column. If we seek backwards, just set p
+ #
+ def seek(self, _index: int):
+ if _index<=self._index:
+ self._index = _index # just jump; don't update stream state (line, ...)
+ return
+ # seek forward
+ self._index = min(_index, self._size)
+
+ def getText(self, start :int, stop: int):
+ if stop >= self._size:
+ stop = self._size-1
+ if start >= self._size:
+ return ""
+ else:
+ return self.strdata[start:stop+1]
+
+ def __str__(self):
+ return self.strdata
diff --git a/cameras/deps/antlr4/IntervalSet.py b/cameras/deps/antlr4/IntervalSet.py
new file mode 100644
index 0000000000000000000000000000000000000000..fda8e6da3f69ba12147fdcb61268f7000d55d635
--- /dev/null
+++ b/cameras/deps/antlr4/IntervalSet.py
@@ -0,0 +1,180 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+from io import StringIO
+from antlr4.Token import Token
+
+# need forward declarations
+IntervalSet = None
+
+class IntervalSet(object):
+ __slots__ = ('intervals', 'readonly')
+
+ def __init__(self):
+ self.intervals = None
+ self.readonly = False
+
+ def __iter__(self):
+ if self.intervals is not None:
+ for i in self.intervals:
+ for c in i:
+ yield c
+
+ def __getitem__(self, item):
+ i = 0
+ for k in self:
+ if i==item:
+ return k
+ else:
+ i += 1
+ return Token.INVALID_TYPE
+
+ def addOne(self, v:int):
+ self.addRange(range(v, v+1))
+
+ def addRange(self, v:range):
+ if self.intervals is None:
+ self.intervals = list()
+ self.intervals.append(v)
+ else:
+ # find insert pos
+ k = 0
+ for i in self.intervals:
+ # distinct range -> insert
+ if v.stop adjust
+ elif v.stop==i.start:
+ self.intervals[k] = range(v.start, i.stop)
+ return
+ # overlapping range -> adjust and reduce
+ elif v.start<=i.stop:
+ self.intervals[k] = range(min(i.start,v.start), max(i.stop,v.stop))
+ self.reduce(k)
+ return
+ k += 1
+ # greater than any existing
+ self.intervals.append(v)
+
+ def addSet(self, other:IntervalSet):
+ if other.intervals is not None:
+ for i in other.intervals:
+ self.addRange(i)
+ return self
+
+ def reduce(self, k:int):
+ # only need to reduce if k is not the last
+ if k= r.stop:
+ self.intervals.pop(k+1)
+ self.reduce(k)
+ elif l.stop >= r.start:
+ self.intervals[k] = range(l.start, r.stop)
+ self.intervals.pop(k+1)
+
+ def complement(self, start, stop):
+ result = IntervalSet()
+ result.addRange(range(start,stop+1))
+ for i in self.intervals:
+ result.removeRange(i)
+ return result
+
+ def __contains__(self, item):
+ if self.intervals is None:
+ return False
+ else:
+ return any(item in i for i in self.intervals)
+
+ def __len__(self):
+ return sum(len(i) for i in self.intervals)
+
+ def removeRange(self, v):
+ if v.start==v.stop-1:
+ self.removeOne(v.start)
+ elif self.intervals is not None:
+ k = 0
+ for i in self.intervals:
+ # intervals are ordered
+ if v.stop<=i.start:
+ return
+ # check for including range, split it
+ elif v.start>i.start and v.stop=i.stop:
+ self.intervals.pop(k)
+ k -= 1 # need another pass
+ # check for lower boundary
+ elif v.start1:
+ buf.write("{")
+ first = True
+ for i in self.intervals:
+ for j in i:
+ if not first:
+ buf.write(", ")
+ buf.write(self.elementName(literalNames, symbolicNames, j))
+ first = False
+ if len(self)>1:
+ buf.write("}")
+ return buf.getvalue()
+
+ def elementName(self, literalNames:list, symbolicNames:list, a:int):
+ if a==Token.EOF:
+ return ""
+ elif a==Token.EPSILON:
+ return ""
+ else:
+ if a":
+ return literalNames[a]
+ if a"
diff --git a/cameras/deps/antlr4/LL1Analyzer.py b/cameras/deps/antlr4/LL1Analyzer.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac149fa2f1d23903ef60593521291c4d9f1945a4
--- /dev/null
+++ b/cameras/deps/antlr4/LL1Analyzer.py
@@ -0,0 +1,173 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+from antlr4.IntervalSet import IntervalSet
+from antlr4.Token import Token
+from antlr4.PredictionContext import PredictionContext, SingletonPredictionContext, PredictionContextFromRuleContext
+from antlr4.RuleContext import RuleContext
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNConfig import ATNConfig
+from antlr4.atn.ATNState import ATNState, RuleStopState
+from antlr4.atn.Transition import WildcardTransition, NotSetTransition, AbstractPredicateTransition, RuleTransition
+
+
+class LL1Analyzer (object):
+ __slots__ = 'atn'
+
+ #* Special value added to the lookahead sets to indicate that we hit
+ # a predicate during analysis if {@code seeThruPreds==false}.
+ #/
+ HIT_PRED = Token.INVALID_TYPE
+
+ def __init__(self, atn:ATN):
+ self.atn = atn
+
+ #*
+ # Calculates the SLL(1) expected lookahead set for each outgoing transition
+ # of an {@link ATNState}. The returned array has one element for each
+ # outgoing transition in {@code s}. If the closure from transition
+ # i leads to a semantic predicate before matching a symbol, the
+ # element at index i of the result will be {@code null}.
+ #
+ # @param s the ATN state
+ # @return the expected symbols for each outgoing transition of {@code s}.
+ #/
+ def getDecisionLookahead(self, s:ATNState):
+ if s is None:
+ return None
+
+ count = len(s.transitions)
+ look = [] * count
+ for alt in range(0, count):
+ look[alt] = set()
+ lookBusy = set()
+ seeThruPreds = False # fail to get lookahead upon pred
+ self._LOOK(s.transition(alt).target, None, PredictionContext.EMPTY,
+ look[alt], lookBusy, set(), seeThruPreds, False)
+ # Wipe out lookahead for this alternative if we found nothing
+ # or we had a predicate when we !seeThruPreds
+ if len(look[alt])==0 or self.HIT_PRED in look[alt]:
+ look[alt] = None
+ return look
+
+ #*
+ # Compute set of tokens that can follow {@code s} in the ATN in the
+ # specified {@code ctx}.
+ #
+ #
If {@code ctx} is {@code null} and the end of the rule containing
+ # {@code s} is reached, {@link Token#EPSILON} is added to the result set.
+ # If {@code ctx} is not {@code null} and the end of the outermost rule is
+ # reached, {@link Token#EOF} is added to the result set.
+ #
+ # @param s the ATN state
+ # @param stopState the ATN state to stop at. This can be a
+ # {@link BlockEndState} to detect epsilon paths through a closure.
+ # @param ctx the complete parser context, or {@code null} if the context
+ # should be ignored
+ #
+ # @return The set of tokens that can follow {@code s} in the ATN in the
+ # specified {@code ctx}.
+ #/
+ def LOOK(self, s:ATNState, stopState:ATNState=None, ctx:RuleContext=None):
+ r = IntervalSet()
+ seeThruPreds = True # ignore preds; get all lookahead
+ lookContext = PredictionContextFromRuleContext(s.atn, ctx) if ctx is not None else None
+ self._LOOK(s, stopState, lookContext, r, set(), set(), seeThruPreds, True)
+ return r
+
+ #*
+ # Compute set of tokens that can follow {@code s} in the ATN in the
+ # specified {@code ctx}.
+ #
+ #
If {@code ctx} is {@code null} and {@code stopState} or the end of the
+ # rule containing {@code s} is reached, {@link Token#EPSILON} is added to
+ # the result set. If {@code ctx} is not {@code null} and {@code addEOF} is
+ # {@code true} and {@code stopState} or the end of the outermost rule is
+ # reached, {@link Token#EOF} is added to the result set.
+ #
+ # @param s the ATN state.
+ # @param stopState the ATN state to stop at. This can be a
+ # {@link BlockEndState} to detect epsilon paths through a closure.
+ # @param ctx The outer context, or {@code null} if the outer context should
+ # not be used.
+ # @param look The result lookahead set.
+ # @param lookBusy A set used for preventing epsilon closures in the ATN
+ # from causing a stack overflow. Outside code should pass
+ # {@code new HashSet} for this argument.
+ # @param calledRuleStack A set used for preventing left recursion in the
+ # ATN from causing a stack overflow. Outside code should pass
+ # {@code new BitSet()} for this argument.
+ # @param seeThruPreds {@code true} to true semantic predicates as
+ # implicitly {@code true} and "see through them", otherwise {@code false}
+ # to treat semantic predicates as opaque and add {@link #HIT_PRED} to the
+ # result if one is encountered.
+ # @param addEOF Add {@link Token#EOF} to the result if the end of the
+ # outermost context is reached. This parameter has no effect if {@code ctx}
+ # is {@code null}.
+ #/
+ def _LOOK(self, s:ATNState, stopState:ATNState , ctx:PredictionContext, look:IntervalSet, lookBusy:set,
+ calledRuleStack:set, seeThruPreds:bool, addEOF:bool):
+ c = ATNConfig(s, 0, ctx)
+
+ if c in lookBusy:
+ return
+ lookBusy.add(c)
+
+ if s == stopState:
+ if ctx is None:
+ look.addOne(Token.EPSILON)
+ return
+ elif ctx.isEmpty() and addEOF:
+ look.addOne(Token.EOF)
+ return
+
+ if isinstance(s, RuleStopState ):
+ if ctx is None:
+ look.addOne(Token.EPSILON)
+ return
+ elif ctx.isEmpty() and addEOF:
+ look.addOne(Token.EOF)
+ return
+
+ if ctx != PredictionContext.EMPTY:
+ removed = s.ruleIndex in calledRuleStack
+ try:
+ calledRuleStack.discard(s.ruleIndex)
+ # run thru all possible stack tops in ctx
+ for i in range(0, len(ctx)):
+ returnState = self.atn.states[ctx.getReturnState(i)]
+ self._LOOK(returnState, stopState, ctx.getParent(i), look, lookBusy, calledRuleStack, seeThruPreds, addEOF)
+ finally:
+ if removed:
+ calledRuleStack.add(s.ruleIndex)
+ return
+
+ for t in s.transitions:
+ if type(t) == RuleTransition:
+ if t.target.ruleIndex in calledRuleStack:
+ continue
+
+ newContext = SingletonPredictionContext.create(ctx, t.followState.stateNumber)
+
+ try:
+ calledRuleStack.add(t.target.ruleIndex)
+ self._LOOK(t.target, stopState, newContext, look, lookBusy, calledRuleStack, seeThruPreds, addEOF)
+ finally:
+ calledRuleStack.remove(t.target.ruleIndex)
+ elif isinstance(t, AbstractPredicateTransition ):
+ if seeThruPreds:
+ self._LOOK(t.target, stopState, ctx, look, lookBusy, calledRuleStack, seeThruPreds, addEOF)
+ else:
+ look.addOne(self.HIT_PRED)
+ elif t.isEpsilon:
+ self._LOOK(t.target, stopState, ctx, look, lookBusy, calledRuleStack, seeThruPreds, addEOF)
+ elif type(t) == WildcardTransition:
+ look.addRange( range(Token.MIN_USER_TOKEN_TYPE, self.atn.maxTokenType + 1) )
+ else:
+ set_ = t.label
+ if set_ is not None:
+ if isinstance(t, NotSetTransition):
+ set_ = set_.complement(Token.MIN_USER_TOKEN_TYPE, self.atn.maxTokenType)
+ look.addSet(set_)
diff --git a/cameras/deps/antlr4/Lexer.py b/cameras/deps/antlr4/Lexer.py
new file mode 100644
index 0000000000000000000000000000000000000000..82accadcbca680b48f4c0206731259d32adc299e
--- /dev/null
+++ b/cameras/deps/antlr4/Lexer.py
@@ -0,0 +1,329 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+# A lexer is recognizer that draws input symbols from a character stream.
+# lexer grammars result in a subclass of self object. A Lexer object
+# uses simplified match() and error recovery mechanisms in the interest
+# of speed.
+#/
+from io import StringIO
+
+import sys
+if sys.version_info[1] > 5:
+ from typing import TextIO
+else:
+ from typing.io import TextIO
+from antlr4.CommonTokenFactory import CommonTokenFactory
+from antlr4.atn.LexerATNSimulator import LexerATNSimulator
+from antlr4.InputStream import InputStream
+from antlr4.Recognizer import Recognizer
+from antlr4.Token import Token
+from antlr4.error.Errors import IllegalStateException, LexerNoViableAltException, RecognitionException
+
+class TokenSource(object):
+
+ pass
+
+
+class Lexer(Recognizer, TokenSource):
+ __slots__ = (
+ '_input', '_output', '_factory', '_tokenFactorySourcePair', '_token',
+ '_tokenStartCharIndex', '_tokenStartLine', '_tokenStartColumn',
+ '_hitEOF', '_channel', '_type', '_modeStack', '_mode', '_text'
+ )
+
+ DEFAULT_MODE = 0
+ MORE = -2
+ SKIP = -3
+
+ DEFAULT_TOKEN_CHANNEL = Token.DEFAULT_CHANNEL
+ HIDDEN = Token.HIDDEN_CHANNEL
+ MIN_CHAR_VALUE = 0x0000
+ MAX_CHAR_VALUE = 0x10FFFF
+
+ def __init__(self, input:InputStream, output:TextIO = sys.stdout):
+ super().__init__()
+ self._input = input
+ self._output = output
+ self._factory = CommonTokenFactory.DEFAULT
+ self._tokenFactorySourcePair = (self, input)
+
+ self._interp = None # child classes must populate this
+
+ # The goal of all lexer rules/methods is to create a token object.
+ # self is an instance variable as multiple rules may collaborate to
+ # create a single token. nextToken will return self object after
+ # matching lexer rule(s). If you subclass to allow multiple token
+ # emissions, then set self to the last token to be matched or
+ # something nonnull so that the auto token emit mechanism will not
+ # emit another token.
+ self._token = None
+
+ # What character index in the stream did the current token start at?
+ # Needed, for example, to get the text for current token. Set at
+ # the start of nextToken.
+ self._tokenStartCharIndex = -1
+
+ # The line on which the first character of the token resides#/
+ self._tokenStartLine = -1
+
+ # The character position of first character within the line#/
+ self._tokenStartColumn = -1
+
+ # Once we see EOF on char stream, next token will be EOF.
+ # If you have DONE : EOF ; then you see DONE EOF.
+ self._hitEOF = False
+
+ # The channel number for the current token#/
+ self._channel = Token.DEFAULT_CHANNEL
+
+ # The token type for the current token#/
+ self._type = Token.INVALID_TYPE
+
+ self._modeStack = []
+ self._mode = self.DEFAULT_MODE
+
+ # You can set the text for the current token to override what is in
+ # the input char buffer. Use setText() or can set self instance var.
+ #/
+ self._text = None
+
+
+ def reset(self):
+ # wack Lexer state variables
+ if self._input is not None:
+ self._input.seek(0) # rewind the input
+ self._token = None
+ self._type = Token.INVALID_TYPE
+ self._channel = Token.DEFAULT_CHANNEL
+ self._tokenStartCharIndex = -1
+ self._tokenStartColumn = -1
+ self._tokenStartLine = -1
+ self._text = None
+
+ self._hitEOF = False
+ self._mode = Lexer.DEFAULT_MODE
+ self._modeStack = []
+
+ self._interp.reset()
+
+ # Return a token from self source; i.e., match a token on the char
+ # stream.
+ def nextToken(self):
+ if self._input is None:
+ raise IllegalStateException("nextToken requires a non-null input stream.")
+
+ # Mark start location in char stream so unbuffered streams are
+ # guaranteed at least have text of current token
+ tokenStartMarker = self._input.mark()
+ try:
+ while True:
+ if self._hitEOF:
+ self.emitEOF()
+ return self._token
+ self._token = None
+ self._channel = Token.DEFAULT_CHANNEL
+ self._tokenStartCharIndex = self._input.index
+ self._tokenStartColumn = self._interp.column
+ self._tokenStartLine = self._interp.line
+ self._text = None
+ continueOuter = False
+ while True:
+ self._type = Token.INVALID_TYPE
+ ttype = self.SKIP
+ try:
+ ttype = self._interp.match(self._input, self._mode)
+ except LexerNoViableAltException as e:
+ self.notifyListeners(e) # report error
+ self.recover(e)
+ if self._input.LA(1)==Token.EOF:
+ self._hitEOF = True
+ if self._type == Token.INVALID_TYPE:
+ self._type = ttype
+ if self._type == self.SKIP:
+ continueOuter = True
+ break
+ if self._type!=self.MORE:
+ break
+ if continueOuter:
+ continue
+ if self._token is None:
+ self.emit()
+ return self._token
+ finally:
+ # make sure we release marker after match or
+ # unbuffered char stream will keep buffering
+ self._input.release(tokenStartMarker)
+
+ # Instruct the lexer to skip creating a token for current lexer rule
+ # and look for another token. nextToken() knows to keep looking when
+ # a lexer rule finishes with token set to SKIP_TOKEN. Recall that
+ # if token==null at end of any token rule, it creates one for you
+ # and emits it.
+ #/
+ def skip(self):
+ self._type = self.SKIP
+
+ def more(self):
+ self._type = self.MORE
+
+ def mode(self, m:int):
+ self._mode = m
+
+ def pushMode(self, m:int):
+ if self._interp.debug:
+ print("pushMode " + str(m), file=self._output)
+ self._modeStack.append(self._mode)
+ self.mode(m)
+
+ def popMode(self):
+ if len(self._modeStack)==0:
+ raise Exception("Empty Stack")
+ if self._interp.debug:
+ print("popMode back to "+ self._modeStack[:-1], file=self._output)
+ self.mode( self._modeStack.pop() )
+ return self._mode
+
+ # Set the char stream and reset the lexer#/
+ @property
+ def inputStream(self):
+ return self._input
+
+ @inputStream.setter
+ def inputStream(self, input:InputStream):
+ self._input = None
+ self._tokenFactorySourcePair = (self, self._input)
+ self.reset()
+ self._input = input
+ self._tokenFactorySourcePair = (self, self._input)
+
+ @property
+ def sourceName(self):
+ return self._input.sourceName
+
+ # By default does not support multiple emits per nextToken invocation
+ # for efficiency reasons. Subclass and override self method, nextToken,
+ # and getToken (to push tokens into a list and pull from that list
+ # rather than a single variable as self implementation does).
+ #/
+ def emitToken(self, token:Token):
+ self._token = token
+
+ # The standard method called to automatically emit a token at the
+ # outermost lexical rule. The token object should point into the
+ # char buffer start..stop. If there is a text override in 'text',
+ # use that to set the token's text. Override self method to emit
+ # custom Token objects or provide a new factory.
+ #/
+ def emit(self):
+ t = self._factory.create(self._tokenFactorySourcePair, self._type, self._text, self._channel, self._tokenStartCharIndex,
+ self.getCharIndex()-1, self._tokenStartLine, self._tokenStartColumn)
+ self.emitToken(t)
+ return t
+
+ def emitEOF(self):
+ cpos = self.column
+ lpos = self.line
+ eof = self._factory.create(self._tokenFactorySourcePair, Token.EOF, None, Token.DEFAULT_CHANNEL, self._input.index,
+ self._input.index-1, lpos, cpos)
+ self.emitToken(eof)
+ return eof
+
+ @property
+ def type(self):
+ return self._type
+
+ @type.setter
+ def type(self, type:int):
+ self._type = type
+
+ @property
+ def line(self):
+ return self._interp.line
+
+ @line.setter
+ def line(self, line:int):
+ self._interp.line = line
+
+ @property
+ def column(self):
+ return self._interp.column
+
+ @column.setter
+ def column(self, column:int):
+ self._interp.column = column
+
+ # What is the index of the current character of lookahead?#/
+ def getCharIndex(self):
+ return self._input.index
+
+ # Return the text matched so far for the current token or any
+ # text override.
+ @property
+ def text(self):
+ if self._text is not None:
+ return self._text
+ else:
+ return self._interp.getText(self._input)
+
+ # Set the complete text of self token; it wipes any previous
+ # changes to the text.
+ @text.setter
+ def text(self, txt:str):
+ self._text = txt
+
+ # Return a list of all Token objects in input char stream.
+ # Forces load of all tokens. Does not include EOF token.
+ #/
+ def getAllTokens(self):
+ tokens = []
+ t = self.nextToken()
+ while t.type!=Token.EOF:
+ tokens.append(t)
+ t = self.nextToken()
+ return tokens
+
+ def notifyListeners(self, e:LexerNoViableAltException):
+ start = self._tokenStartCharIndex
+ stop = self._input.index
+ text = self._input.getText(start, stop)
+ msg = "token recognition error at: '" + self.getErrorDisplay(text) + "'"
+ listener = self.getErrorListenerDispatch()
+ listener.syntaxError(self, None, self._tokenStartLine, self._tokenStartColumn, msg, e)
+
+ def getErrorDisplay(self, s:str):
+ with StringIO() as buf:
+ for c in s:
+ buf.write(self.getErrorDisplayForChar(c))
+ return buf.getvalue()
+
+ def getErrorDisplayForChar(self, c:str):
+ if ord(c[0])==Token.EOF:
+ return ""
+ elif c=='\n':
+ return "\\n"
+ elif c=='\t':
+ return "\\t"
+ elif c=='\r':
+ return "\\r"
+ else:
+ return c
+
+ def getCharErrorDisplay(self, c:str):
+ return "'" + self.getErrorDisplayForChar(c) + "'"
+
+ # Lexers can normally match any char in it's vocabulary after matching
+ # a token, so do the easy thing and just kill a character and hope
+ # it all works out. You can instead use the rule invocation stack
+ # to do sophisticated error recovery if you are in a fragment rule.
+ #/
+ def recover(self, re:RecognitionException):
+ if self._input.LA(1) != Token.EOF:
+ if isinstance(re, LexerNoViableAltException):
+ # skip a char and try again
+ self._interp.consume(self._input)
+ else:
+ # TODO: Do we lose character or line position information?
+ self._input.consume()
diff --git a/cameras/deps/antlr4/ListTokenSource.py b/cameras/deps/antlr4/ListTokenSource.py
new file mode 100644
index 0000000000000000000000000000000000000000..40bcaf94a243e7bd7d2c9b9bfa7f522706a46788
--- /dev/null
+++ b/cameras/deps/antlr4/ListTokenSource.py
@@ -0,0 +1,144 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# Provides an implementation of {@link TokenSource} as a wrapper around a list
+# of {@link Token} objects.
+#
+#
If the final token in the list is an {@link Token#EOF} token, it will be used
+# as the EOF token for every call to {@link #nextToken} after the end of the
+# list is reached. Otherwise, an EOF token will be created.
+#
+from antlr4.CommonTokenFactory import CommonTokenFactory
+from antlr4.Lexer import TokenSource
+from antlr4.Token import Token
+
+
+class ListTokenSource(TokenSource):
+ __slots__ = ('tokens', 'sourceName', 'pos', 'eofToken', '_factory')
+
+ # Constructs a new {@link ListTokenSource} instance from the specified
+ # collection of {@link Token} objects and source name.
+ #
+ # @param tokens The collection of {@link Token} objects to provide as a
+ # {@link TokenSource}.
+ # @param sourceName The name of the {@link TokenSource}. If this value is
+ # {@code null}, {@link #getSourceName} will attempt to infer the name from
+ # the next {@link Token} (or the previous token if the end of the input has
+ # been reached).
+ #
+ # @exception NullPointerException if {@code tokens} is {@code null}
+ #
+ def __init__(self, tokens:list, sourceName:str=None):
+ if tokens is None:
+ raise ReferenceError("tokens cannot be null")
+ self.tokens = tokens
+ self.sourceName = sourceName
+ # The index into {@link #tokens} of token to return by the next call to
+ # {@link #nextToken}. The end of the input is indicated by this value
+ # being greater than or equal to the number of items in {@link #tokens}.
+ self.pos = 0
+ # This field caches the EOF token for the token source.
+ self.eofToken = None
+ # This is the backing field for {@link #getTokenFactory} and
+ self._factory = CommonTokenFactory.DEFAULT
+
+
+ #
+ # {@inheritDoc}
+ #
+ @property
+ def column(self):
+ if self.pos < len(self.tokens):
+ return self.tokens[self.pos].column
+ elif self.eofToken is not None:
+ return self.eofToken.column
+ elif len(self.tokens) > 0:
+ # have to calculate the result from the line/column of the previous
+ # token, along with the text of the token.
+ lastToken = self.tokens[len(self.tokens) - 1]
+ tokenText = lastToken.text
+ if tokenText is not None:
+ lastNewLine = tokenText.rfind('\n')
+ if lastNewLine >= 0:
+ return len(tokenText) - lastNewLine - 1
+ return lastToken.column + lastToken.stop - lastToken.start + 1
+
+ # only reach this if tokens is empty, meaning EOF occurs at the first
+ # position in the input
+ return 0
+
+ #
+ # {@inheritDoc}
+ #
+ def nextToken(self):
+ if self.pos >= len(self.tokens):
+ if self.eofToken is None:
+ start = -1
+ if len(self.tokens) > 0:
+ previousStop = self.tokens[len(self.tokens) - 1].stop
+ if previousStop != -1:
+ start = previousStop + 1
+ stop = max(-1, start - 1)
+ self.eofToken = self._factory.create((self, self.getInputStream()),
+ Token.EOF, "EOF", Token.DEFAULT_CHANNEL, start, stop, self.line, self.column)
+ return self.eofToken
+ t = self.tokens[self.pos]
+ if self.pos == len(self.tokens) - 1 and t.type == Token.EOF:
+ self.eofToken = t
+ self.pos += 1
+ return t
+
+ #
+ # {@inheritDoc}
+ #
+ @property
+ def line(self):
+ if self.pos < len(self.tokens):
+ return self.tokens[self.pos].line
+ elif self.eofToken is not None:
+ return self.eofToken.line
+ elif len(self.tokens) > 0:
+ # have to calculate the result from the line/column of the previous
+ # token, along with the text of the token.
+ lastToken = self.tokens[len(self.tokens) - 1]
+ line = lastToken.line
+ tokenText = lastToken.text
+ if tokenText is not None:
+ line += tokenText.count('\n')
+
+ # if no text is available, assume the token did not contain any newline characters.
+ return line
+
+ # only reach this if tokens is empty, meaning EOF occurs at the first
+ # position in the input
+ return 1
+
+ #
+ # {@inheritDoc}
+ #
+ def getInputStream(self):
+ if self.pos < len(self.tokens):
+ return self.tokens[self.pos].getInputStream()
+ elif self.eofToken is not None:
+ return self.eofToken.getInputStream()
+ elif len(self.tokens) > 0:
+ return self.tokens[len(self.tokens) - 1].getInputStream()
+ else:
+ # no input stream information is available
+ return None
+
+ #
+ # {@inheritDoc}
+ #
+ def getSourceName(self):
+ if self.sourceName is not None:
+ return self.sourceName
+ inputStream = self.getInputStream()
+ if inputStream is not None:
+ return inputStream.getSourceName()
+ else:
+ return "List"
diff --git a/cameras/deps/antlr4/Parser.py b/cameras/deps/antlr4/Parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae7f48cce07bebc77f03a577278b95a45832fda0
--- /dev/null
+++ b/cameras/deps/antlr4/Parser.py
@@ -0,0 +1,580 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+import sys
+if sys.version_info[1] > 5:
+ from typing import TextIO
+else:
+ from typing.io import TextIO
+from antlr4.BufferedTokenStream import TokenStream
+from antlr4.CommonTokenFactory import TokenFactory
+from antlr4.error.ErrorStrategy import DefaultErrorStrategy
+from antlr4.InputStream import InputStream
+from antlr4.Recognizer import Recognizer
+from antlr4.RuleContext import RuleContext
+from antlr4.ParserRuleContext import ParserRuleContext
+from antlr4.Token import Token
+from antlr4.Lexer import Lexer
+from antlr4.atn.ATNDeserializer import ATNDeserializer
+from antlr4.atn.ATNDeserializationOptions import ATNDeserializationOptions
+from antlr4.error.Errors import UnsupportedOperationException, RecognitionException
+from antlr4.tree.ParseTreePatternMatcher import ParseTreePatternMatcher
+from antlr4.tree.Tree import ParseTreeListener, TerminalNode, ErrorNode
+
+class TraceListener(ParseTreeListener):
+ __slots__ = '_parser'
+
+ def __init__(self, parser):
+ self._parser = parser
+
+ def enterEveryRule(self, ctx):
+ print("enter " + self._parser.ruleNames[ctx.getRuleIndex()] + ", LT(1)=" + self._parser._input.LT(1).text, file=self._parser._output)
+
+ def visitTerminal(self, node):
+
+ print("consume " + str(node.symbol) + " rule " + self._parser.ruleNames[self._parser._ctx.getRuleIndex()], file=self._parser._output)
+
+ def visitErrorNode(self, node):
+ pass
+
+
+ def exitEveryRule(self, ctx):
+ print("exit " + self._parser.ruleNames[ctx.getRuleIndex()] + ", LT(1)=" + self._parser._input.LT(1).text, file=self._parser._output)
+
+
+# self is all the parsing support code essentially; most of it is error recovery stuff.#
+class Parser (Recognizer):
+ __slots__ = (
+ '_input', '_output', '_errHandler', '_precedenceStack', '_ctx',
+ 'buildParseTrees', '_tracer', '_parseListeners', '_syntaxErrors'
+
+ )
+ # self field maps from the serialized ATN string to the deserialized {@link ATN} with
+ # bypass alternatives.
+ #
+ # @see ATNDeserializationOptions#isGenerateRuleBypassTransitions()
+ #
+ bypassAltsAtnCache = dict()
+
+ def __init__(self, input:TokenStream, output:TextIO = sys.stdout):
+ super().__init__()
+ # The input stream.
+ self._input = None
+ self._output = output
+ # The error handling strategy for the parser. The default value is a new
+ # instance of {@link DefaultErrorStrategy}.
+ self._errHandler = DefaultErrorStrategy()
+ self._precedenceStack = list()
+ self._precedenceStack.append(0)
+ # The {@link ParserRuleContext} object for the currently executing rule.
+ # self is always non-null during the parsing process.
+ self._ctx = None
+ # Specifies whether or not the parser should construct a parse tree during
+ # the parsing process. The default value is {@code true}.
+ self.buildParseTrees = True
+ # When {@link #setTrace}{@code (true)} is called, a reference to the
+ # {@link TraceListener} is stored here so it can be easily removed in a
+ # later call to {@link #setTrace}{@code (false)}. The listener itself is
+ # implemented as a parser listener so self field is not directly used by
+ # other parser methods.
+ self._tracer = None
+ # The list of {@link ParseTreeListener} listeners registered to receive
+ # events during the parse.
+ self._parseListeners = None
+ # The number of syntax errors reported during parsing. self value is
+ # incremented each time {@link #notifyErrorListeners} is called.
+ self._syntaxErrors = 0
+ self.setInputStream(input)
+
+ # reset the parser's state#
+ def reset(self):
+ if self._input is not None:
+ self._input.seek(0)
+ self._errHandler.reset(self)
+ self._ctx = None
+ self._syntaxErrors = 0
+ self.setTrace(False)
+ self._precedenceStack = list()
+ self._precedenceStack.append(0)
+ if self._interp is not None:
+ self._interp.reset()
+
+ # Match current input symbol against {@code ttype}. If the symbol type
+ # matches, {@link ANTLRErrorStrategy#reportMatch} and {@link #consume} are
+ # called to complete the match process.
+ #
+ #
If the symbol type does not match,
+ # {@link ANTLRErrorStrategy#recoverInline} is called on the current error
+ # strategy to attempt recovery. If {@link #getBuildParseTree} is
+ # {@code true} and the token index of the symbol returned by
+ # {@link ANTLRErrorStrategy#recoverInline} is -1, the symbol is added to
+ # the parse tree by calling {@link ParserRuleContext#addErrorNode}.
+ #
+ # @param ttype the token type to match
+ # @return the matched symbol
+ # @throws RecognitionException if the current input symbol did not match
+ # {@code ttype} and the error strategy could not recover from the
+ # mismatched symbol
+
+ def match(self, ttype:int):
+ t = self.getCurrentToken()
+ if t.type==ttype:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ else:
+ t = self._errHandler.recoverInline(self)
+ if self.buildParseTrees and t.tokenIndex==-1:
+ # we must have conjured up a new token during single token insertion
+ # if it's not the current symbol
+ self._ctx.addErrorNode(t)
+ return t
+
+ # Match current input symbol as a wildcard. If the symbol type matches
+ # (i.e. has a value greater than 0), {@link ANTLRErrorStrategy#reportMatch}
+ # and {@link #consume} are called to complete the match process.
+ #
+ #
If the symbol type does not match,
+ # {@link ANTLRErrorStrategy#recoverInline} is called on the current error
+ # strategy to attempt recovery. If {@link #getBuildParseTree} is
+ # {@code true} and the token index of the symbol returned by
+ # {@link ANTLRErrorStrategy#recoverInline} is -1, the symbol is added to
+ # the parse tree by calling {@link ParserRuleContext#addErrorNode}.
+ #
+ # @return the matched symbol
+ # @throws RecognitionException if the current input symbol did not match
+ # a wildcard and the error strategy could not recover from the mismatched
+ # symbol
+
+ def matchWildcard(self):
+ t = self.getCurrentToken()
+ if t.type > 0:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ else:
+ t = self._errHandler.recoverInline(self)
+ if self.buildParseTrees and t.tokenIndex == -1:
+ # we must have conjured up a new token during single token insertion
+ # if it's not the current symbol
+ self._ctx.addErrorNode(t)
+
+ return t
+
+ def getParseListeners(self):
+ return list() if self._parseListeners is None else self._parseListeners
+
+ # Registers {@code listener} to receive events during the parsing process.
+ #
+ #
To support output-preserving grammar transformations (including but not
+ # limited to left-recursion removal, automated left-factoring, and
+ # optimized code generation), calls to listener methods during the parse
+ # may differ substantially from calls made by
+ # {@link ParseTreeWalker#DEFAULT} used after the parse is complete. In
+ # particular, rule entry and exit events may occur in a different order
+ # during the parse than after the parser. In addition, calls to certain
+ # rule entry methods may be omitted.
+ #
+ #
With the following specific exceptions, calls to listener events are
+ # deterministic, i.e. for identical input the calls to listener
+ # methods will be the same.
+ #
+ #
+ #
Alterations to the grammar used to generate code may change the
+ # behavior of the listener calls.
+ #
Alterations to the command line options passed to ANTLR 4 when
+ # generating the parser may change the behavior of the listener calls.
+ #
Changing the version of the ANTLR Tool used to generate the parser
+ # may change the behavior of the listener calls.
+ #
+ #
+ # @param listener the listener to add
+ #
+ # @throws NullPointerException if {@code} listener is {@code null}
+ #
+ def addParseListener(self, listener:ParseTreeListener):
+ if listener is None:
+ raise ReferenceError("listener")
+ if self._parseListeners is None:
+ self._parseListeners = []
+ self._parseListeners.append(listener)
+
+ #
+ # Remove {@code listener} from the list of parse listeners.
+ #
+ #
If {@code listener} is {@code null} or has not been added as a parse
+ # listener, self method does nothing.
+ # @param listener the listener to remove
+ #
+ def removeParseListener(self, listener:ParseTreeListener):
+ if self._parseListeners is not None:
+ self._parseListeners.remove(listener)
+ if len(self._parseListeners)==0:
+ self._parseListeners = None
+
+ # Remove all parse listeners.
+ def removeParseListeners(self):
+ self._parseListeners = None
+
+ # Notify any parse listeners of an enter rule event.
+ def triggerEnterRuleEvent(self):
+ if self._parseListeners is not None:
+ for listener in self._parseListeners:
+ listener.enterEveryRule(self._ctx)
+ self._ctx.enterRule(listener)
+
+ #
+ # Notify any parse listeners of an exit rule event.
+ #
+ # @see #addParseListener
+ #
+ def triggerExitRuleEvent(self):
+ if self._parseListeners is not None:
+ # reverse order walk of listeners
+ for listener in reversed(self._parseListeners):
+ self._ctx.exitRule(listener)
+ listener.exitEveryRule(self._ctx)
+
+
+ # Gets the number of syntax errors reported during parsing. This value is
+ # incremented each time {@link #notifyErrorListeners} is called.
+ #
+ # @see #notifyErrorListeners
+ #
+ def getNumberOfSyntaxErrors(self):
+ return self._syntaxErrors
+
+ def getTokenFactory(self):
+ return self._input.tokenSource._factory
+
+ # Tell our token source and error strategy about a new way to create tokens.#
+ def setTokenFactory(self, factory:TokenFactory):
+ self._input.tokenSource._factory = factory
+
+ # The ATN with bypass alternatives is expensive to create so we create it
+ # lazily.
+ #
+ # @throws UnsupportedOperationException if the current parser does not
+ # implement the {@link #getSerializedATN()} method.
+ #
+ def getATNWithBypassAlts(self):
+ serializedAtn = self.getSerializedATN()
+ if serializedAtn is None:
+ raise UnsupportedOperationException("The current parser does not support an ATN with bypass alternatives.")
+ result = self.bypassAltsAtnCache.get(serializedAtn, None)
+ if result is None:
+ deserializationOptions = ATNDeserializationOptions()
+ deserializationOptions.generateRuleBypassTransitions = True
+ result = ATNDeserializer(deserializationOptions).deserialize(serializedAtn)
+ self.bypassAltsAtnCache[serializedAtn] = result
+ return result
+
+ # The preferred method of getting a tree pattern. For example, here's a
+ # sample use:
+ #
+ #
+ # ParseTree t = parser.expr();
+ # ParseTreePattern p = parser.compileParseTreePattern("<ID>+0", MyParser.RULE_expr);
+ # ParseTreeMatch m = p.match(t);
+ # String id = m.get("ID");
+ #
+ #
+ def compileParseTreePattern(self, pattern:str, patternRuleIndex:int, lexer:Lexer = None):
+ if lexer is None:
+ if self.getTokenStream() is not None:
+ tokenSource = self.getTokenStream().tokenSource
+ if isinstance( tokenSource, Lexer ):
+ lexer = tokenSource
+ if lexer is None:
+ raise UnsupportedOperationException("Parser can't discover a lexer to use")
+
+ m = ParseTreePatternMatcher(lexer, self)
+ return m.compile(pattern, patternRuleIndex)
+
+
+ def getInputStream(self):
+ return self.getTokenStream()
+
+ def setInputStream(self, input:InputStream):
+ self.setTokenStream(input)
+
+ def getTokenStream(self):
+ return self._input
+
+ # Set the token stream and reset the parser.#
+ def setTokenStream(self, input:TokenStream):
+ self._input = None
+ self.reset()
+ self._input = input
+
+ # Match needs to return the current input symbol, which gets put
+ # into the label for the associated token ref; e.g., x=ID.
+ #
+ def getCurrentToken(self):
+ return self._input.LT(1)
+
+ def notifyErrorListeners(self, msg:str, offendingToken:Token = None, e:RecognitionException = None):
+ if offendingToken is None:
+ offendingToken = self.getCurrentToken()
+ self._syntaxErrors += 1
+ line = offendingToken.line
+ column = offendingToken.column
+ listener = self.getErrorListenerDispatch()
+ listener.syntaxError(self, offendingToken, line, column, msg, e)
+
+ #
+ # Consume and return the {@linkplain #getCurrentToken current symbol}.
+ #
+ #
E.g., given the following input with {@code A} being the current
+ # lookahead symbol, self function moves the cursor to {@code B} and returns
+ # {@code A}.
+ #
+ #
+ # A B
+ # ^
+ #
+ #
+ # If the parser is not in error recovery mode, the consumed symbol is added
+ # to the parse tree using {@link ParserRuleContext#addChild(Token)}, and
+ # {@link ParseTreeListener#visitTerminal} is called on any parse listeners.
+ # If the parser is in error recovery mode, the consumed symbol is
+ # added to the parse tree using
+ # {@link ParserRuleContext#addErrorNode(Token)}, and
+ # {@link ParseTreeListener#visitErrorNode} is called on any parse
+ # listeners.
+ #
+ def consume(self):
+ o = self.getCurrentToken()
+ if o.type != Token.EOF:
+ self.getInputStream().consume()
+ hasListener = self._parseListeners is not None and len(self._parseListeners)>0
+ if self.buildParseTrees or hasListener:
+ if self._errHandler.inErrorRecoveryMode(self):
+ node = self._ctx.addErrorNode(o)
+ else:
+ node = self._ctx.addTokenNode(o)
+ if hasListener:
+ for listener in self._parseListeners:
+ if isinstance(node, ErrorNode):
+ listener.visitErrorNode(node)
+ elif isinstance(node, TerminalNode):
+ listener.visitTerminal(node)
+ return o
+
+ def addContextToParseTree(self):
+ # add current context to parent if we have a parent
+ if self._ctx.parentCtx is not None:
+ self._ctx.parentCtx.addChild(self._ctx)
+
+ # Always called by generated parsers upon entry to a rule. Access field
+ # {@link #_ctx} get the current context.
+ #
+ def enterRule(self, localctx:ParserRuleContext , state:int , ruleIndex:int):
+ self.state = state
+ self._ctx = localctx
+ self._ctx.start = self._input.LT(1)
+ if self.buildParseTrees:
+ self.addContextToParseTree()
+ if self._parseListeners is not None:
+ self.triggerEnterRuleEvent()
+
+ def exitRule(self):
+ self._ctx.stop = self._input.LT(-1)
+ # trigger event on _ctx, before it reverts to parent
+ if self._parseListeners is not None:
+ self.triggerExitRuleEvent()
+ self.state = self._ctx.invokingState
+ self._ctx = self._ctx.parentCtx
+
+ def enterOuterAlt(self, localctx:ParserRuleContext, altNum:int):
+ localctx.setAltNumber(altNum)
+ # if we have new localctx, make sure we replace existing ctx
+ # that is previous child of parse tree
+ if self.buildParseTrees and self._ctx != localctx:
+ if self._ctx.parentCtx is not None:
+ self._ctx.parentCtx.removeLastChild()
+ self._ctx.parentCtx.addChild(localctx)
+ self._ctx = localctx
+
+ # Get the precedence level for the top-most precedence rule.
+ #
+ # @return The precedence level for the top-most precedence rule, or -1 if
+ # the parser context is not nested within a precedence rule.
+ #
+ def getPrecedence(self):
+ if len(self._precedenceStack)==0:
+ return -1
+ else:
+ return self._precedenceStack[-1]
+
+ def enterRecursionRule(self, localctx:ParserRuleContext, state:int, ruleIndex:int, precedence:int):
+ self.state = state
+ self._precedenceStack.append(precedence)
+ self._ctx = localctx
+ self._ctx.start = self._input.LT(1)
+ if self._parseListeners is not None:
+ self.triggerEnterRuleEvent() # simulates rule entry for left-recursive rules
+
+ #
+ # Like {@link #enterRule} but for recursive rules.
+ #
+ def pushNewRecursionContext(self, localctx:ParserRuleContext, state:int, ruleIndex:int):
+ previous = self._ctx
+ previous.parentCtx = localctx
+ previous.invokingState = state
+ previous.stop = self._input.LT(-1)
+
+ self._ctx = localctx
+ self._ctx.start = previous.start
+ if self.buildParseTrees:
+ self._ctx.addChild(previous)
+
+ if self._parseListeners is not None:
+ self.triggerEnterRuleEvent() # simulates rule entry for left-recursive rules
+
+ def unrollRecursionContexts(self, parentCtx:ParserRuleContext):
+ self._precedenceStack.pop()
+ self._ctx.stop = self._input.LT(-1)
+ retCtx = self._ctx # save current ctx (return value)
+ # unroll so _ctx is as it was before call to recursive method
+ if self._parseListeners is not None:
+ while self._ctx is not parentCtx:
+ self.triggerExitRuleEvent()
+ self._ctx = self._ctx.parentCtx
+ else:
+ self._ctx = parentCtx
+
+ # hook into tree
+ retCtx.parentCtx = parentCtx
+
+ if self.buildParseTrees and parentCtx is not None:
+ # add return ctx into invoking rule's tree
+ parentCtx.addChild(retCtx)
+
+ def getInvokingContext(self, ruleIndex:int):
+ ctx = self._ctx
+ while ctx is not None:
+ if ctx.getRuleIndex() == ruleIndex:
+ return ctx
+ ctx = ctx.parentCtx
+ return None
+
+
+ def precpred(self, localctx:RuleContext , precedence:int):
+ return precedence >= self._precedenceStack[-1]
+
+ def inContext(self, context:str):
+ # TODO: useful in parser?
+ return False
+
+ #
+ # Checks whether or not {@code symbol} can follow the current state in the
+ # ATN. The behavior of self method is equivalent to the following, but is
+ # implemented such that the complete context-sensitive follow set does not
+ # need to be explicitly constructed.
+ #
+ #
+ #
+ # @param symbol the symbol type to check
+ # @return {@code true} if {@code symbol} can follow the current state in
+ # the ATN, otherwise {@code false}.
+ #
+ def isExpectedToken(self, symbol:int):
+ atn = self._interp.atn
+ ctx = self._ctx
+ s = atn.states[self.state]
+ following = atn.nextTokens(s)
+ if symbol in following:
+ return True
+ if not Token.EPSILON in following:
+ return False
+
+ while ctx is not None and ctx.invokingState>=0 and Token.EPSILON in following:
+ invokingState = atn.states[ctx.invokingState]
+ rt = invokingState.transitions[0]
+ following = atn.nextTokens(rt.followState)
+ if symbol in following:
+ return True
+ ctx = ctx.parentCtx
+
+ if Token.EPSILON in following and symbol == Token.EOF:
+ return True
+ else:
+ return False
+
+ # Computes the set of input symbols which could follow the current parser
+ # state and context, as given by {@link #getState} and {@link #getContext},
+ # respectively.
+ #
+ # @see ATN#getExpectedTokens(int, RuleContext)
+ #
+ def getExpectedTokens(self):
+ return self._interp.atn.getExpectedTokens(self.state, self._ctx)
+
+ def getExpectedTokensWithinCurrentRule(self):
+ atn = self._interp.atn
+ s = atn.states[self.state]
+ return atn.nextTokens(s)
+
+ # Get a rule's index (i.e., {@code RULE_ruleName} field) or -1 if not found.#
+ def getRuleIndex(self, ruleName:str):
+ ruleIndex = self.getRuleIndexMap().get(ruleName, None)
+ if ruleIndex is not None:
+ return ruleIndex
+ else:
+ return -1
+
+ # Return List<String> of the rule names in your parser instance
+ # leading up to a call to the current rule. You could override if
+ # you want more details such as the file/line info of where
+ # in the ATN a rule is invoked.
+ #
+ # this is very useful for error messages.
+ #
+ def getRuleInvocationStack(self, p:RuleContext=None):
+ if p is None:
+ p = self._ctx
+ stack = list()
+ while p is not None:
+ # compute what follows who invoked us
+ ruleIndex = p.getRuleIndex()
+ if ruleIndex<0:
+ stack.append("n/a")
+ else:
+ stack.append(self.ruleNames[ruleIndex])
+ p = p.parentCtx
+ return stack
+
+ # For debugging and other purposes.#
+ def getDFAStrings(self):
+ return [ str(dfa) for dfa in self._interp.decisionToDFA]
+
+ # For debugging and other purposes.#
+ def dumpDFA(self):
+ seenOne = False
+ for i in range(0, len(self._interp.decisionToDFA)):
+ dfa = self._interp.decisionToDFA[i]
+ if len(dfa.states)>0:
+ if seenOne:
+ print(file=self._output)
+ print("Decision " + str(dfa.decision) + ":", file=self._output)
+ print(dfa.toString(self.literalNames, self.symbolicNames), end='', file=self._output)
+ seenOne = True
+
+
+ def getSourceName(self):
+ return self._input.sourceName
+
+ # During a parse is sometimes useful to listen in on the rule entry and exit
+ # events as well as token matches. self is for quick and dirty debugging.
+ #
+ def setTrace(self, trace:bool):
+ if not trace:
+ self.removeParseListener(self._tracer)
+ self._tracer = None
+ else:
+ if self._tracer is not None:
+ self.removeParseListener(self._tracer)
+ self._tracer = TraceListener(self)
+ self.addParseListener(self._tracer)
diff --git a/cameras/deps/antlr4/ParserInterpreter.py b/cameras/deps/antlr4/ParserInterpreter.py
new file mode 100644
index 0000000000000000000000000000000000000000..4de43623cf7d308e841c9484470a93fec510d616
--- /dev/null
+++ b/cameras/deps/antlr4/ParserInterpreter.py
@@ -0,0 +1,170 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+# A parser simulator that mimics what ANTLR's generated
+# parser code does. A ParserATNSimulator is used to make
+# predictions via adaptivePredict but this class moves a pointer through the
+# ATN to simulate parsing. ParserATNSimulator just
+# makes us efficient rather than having to backtrack, for example.
+#
+# This properly creates parse trees even for left recursive rules.
+#
+# We rely on the left recursive rule invocation and special predicate
+# transitions to make left recursive rules work.
+#
+# See TestParserInterpreter for examples.
+#
+from antlr4.dfa.DFA import DFA
+from antlr4.BufferedTokenStream import TokenStream
+from antlr4.Lexer import Lexer
+from antlr4.Parser import Parser
+from antlr4.ParserRuleContext import InterpreterRuleContext, ParserRuleContext
+from antlr4.Token import Token
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNState import StarLoopEntryState, ATNState, LoopEndState
+from antlr4.atn.ParserATNSimulator import ParserATNSimulator
+from antlr4.PredictionContext import PredictionContextCache
+from antlr4.atn.Transition import Transition
+from antlr4.error.Errors import RecognitionException, UnsupportedOperationException, FailedPredicateException
+
+
+class ParserInterpreter(Parser):
+ __slots__ = (
+ 'grammarFileName', 'atn', 'tokenNames', 'ruleNames', 'decisionToDFA',
+ 'sharedContextCache', '_parentContextStack',
+ 'pushRecursionContextStates'
+ )
+
+ def __init__(self, grammarFileName:str, tokenNames:list, ruleNames:list, atn:ATN, input:TokenStream):
+ super().__init__(input)
+ self.grammarFileName = grammarFileName
+ self.atn = atn
+ self.tokenNames = tokenNames
+ self.ruleNames = ruleNames
+ self.decisionToDFA = [ DFA(state) for state in atn.decisionToState ]
+ self.sharedContextCache = PredictionContextCache()
+ self._parentContextStack = list()
+ # identify the ATN states where pushNewRecursionContext must be called
+ self.pushRecursionContextStates = set()
+ for state in atn.states:
+ if not isinstance(state, StarLoopEntryState):
+ continue
+ if state.isPrecedenceDecision:
+ self.pushRecursionContextStates.add(state.stateNumber)
+ # get atn simulator that knows how to do predictions
+ self._interp = ParserATNSimulator(self, atn, self.decisionToDFA, self.sharedContextCache)
+
+ # Begin parsing at startRuleIndex#
+ def parse(self, startRuleIndex:int):
+ startRuleStartState = self.atn.ruleToStartState[startRuleIndex]
+ rootContext = InterpreterRuleContext(None, ATNState.INVALID_STATE_NUMBER, startRuleIndex)
+ if startRuleStartState.isPrecedenceRule:
+ self.enterRecursionRule(rootContext, startRuleStartState.stateNumber, startRuleIndex, 0)
+ else:
+ self.enterRule(rootContext, startRuleStartState.stateNumber, startRuleIndex)
+ while True:
+ p = self.getATNState()
+ if p.stateType==ATNState.RULE_STOP :
+ # pop; return from rule
+ if len(self._ctx)==0:
+ if startRuleStartState.isPrecedenceRule:
+ result = self._ctx
+ parentContext = self._parentContextStack.pop()
+ self.unrollRecursionContexts(parentContext.a)
+ return result
+ else:
+ self.exitRule()
+ return rootContext
+ self.visitRuleStopState(p)
+
+ else:
+ try:
+ self.visitState(p)
+ except RecognitionException as e:
+ self.state = self.atn.ruleToStopState[p.ruleIndex].stateNumber
+ self._ctx.exception = e
+ self._errHandler.reportError(self, e)
+ self._errHandler.recover(self, e)
+
+ def enterRecursionRule(self, localctx:ParserRuleContext, state:int, ruleIndex:int, precedence:int):
+ self._parentContextStack.append((self._ctx, localctx.invokingState))
+ super().enterRecursionRule(localctx, state, ruleIndex, precedence)
+
+ def getATNState(self):
+ return self.atn.states[self.state]
+
+ def visitState(self, p:ATNState):
+ edge = 0
+ if len(p.transitions) > 1:
+ self._errHandler.sync(self)
+ edge = self._interp.adaptivePredict(self._input, p.decision, self._ctx)
+ else:
+ edge = 1
+
+ transition = p.transitions[edge - 1]
+ tt = transition.serializationType
+ if tt==Transition.EPSILON:
+
+ if self.pushRecursionContextStates[p.stateNumber] and not isinstance(transition.target, LoopEndState):
+ t = self._parentContextStack[-1]
+ ctx = InterpreterRuleContext(t[0], t[1], self._ctx.ruleIndex)
+ self.pushNewRecursionContext(ctx, self.atn.ruleToStartState[p.ruleIndex].stateNumber, self._ctx.ruleIndex)
+
+ elif tt==Transition.ATOM:
+
+ self.match(transition.label)
+
+ elif tt in [ Transition.RANGE, Transition.SET, Transition.NOT_SET]:
+
+ if not transition.matches(self._input.LA(1), Token.MIN_USER_TOKEN_TYPE, Lexer.MAX_CHAR_VALUE):
+ self._errHandler.recoverInline(self)
+ self.matchWildcard()
+
+ elif tt==Transition.WILDCARD:
+
+ self.matchWildcard()
+
+ elif tt==Transition.RULE:
+
+ ruleStartState = transition.target
+ ruleIndex = ruleStartState.ruleIndex
+ ctx = InterpreterRuleContext(self._ctx, p.stateNumber, ruleIndex)
+ if ruleStartState.isPrecedenceRule:
+ self.enterRecursionRule(ctx, ruleStartState.stateNumber, ruleIndex, transition.precedence)
+ else:
+ self.enterRule(ctx, transition.target.stateNumber, ruleIndex)
+
+ elif tt==Transition.PREDICATE:
+
+ if not self.sempred(self._ctx, transition.ruleIndex, transition.predIndex):
+ raise FailedPredicateException(self)
+
+ elif tt==Transition.ACTION:
+
+ self.action(self._ctx, transition.ruleIndex, transition.actionIndex)
+
+ elif tt==Transition.PRECEDENCE:
+
+ if not self.precpred(self._ctx, transition.precedence):
+ msg = "precpred(_ctx, " + str(transition.precedence) + ")"
+ raise FailedPredicateException(self, msg)
+
+ else:
+ raise UnsupportedOperationException("Unrecognized ATN transition type.")
+
+ self.state = transition.target.stateNumber
+
+ def visitRuleStopState(self, p:ATNState):
+ ruleStartState = self.atn.ruleToStartState[p.ruleIndex]
+ if ruleStartState.isPrecedenceRule:
+ parentContext = self._parentContextStack.pop()
+ self.unrollRecursionContexts(parentContext.a)
+ self.state = parentContext[1]
+ else:
+ self.exitRule()
+
+ ruleTransition = self.atn.states[self.state].transitions[0]
+ self.state = ruleTransition.followState.stateNumber
diff --git a/cameras/deps/antlr4/ParserRuleContext.py b/cameras/deps/antlr4/ParserRuleContext.py
new file mode 100644
index 0000000000000000000000000000000000000000..f945c5ba94ac394ff7714e4d4e43a86aa31924df
--- /dev/null
+++ b/cameras/deps/antlr4/ParserRuleContext.py
@@ -0,0 +1,186 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+
+#* A rule invocation record for parsing.
+#
+# Contains all of the information about the current rule not stored in the
+# RuleContext. It handles parse tree children list, Any ATN state
+# tracing, and the default values available for rule indications:
+# start, stop, rule index, current alt number, current
+# ATN state.
+#
+# Subclasses made for each rule and grammar track the parameters,
+# return values, locals, and labels specific to that rule. These
+# are the objects that are returned from rules.
+#
+# Note text is not an actual field of a rule return value; it is computed
+# from start and stop using the input stream's toString() method. I
+# could add a ctor to this so that we can pass in and store the input
+# stream, but I'm not sure we want to do that. It would seem to be undefined
+# to get the .text property anyway if the rule matches tokens from multiple
+# input streams.
+#
+# I do not use getters for fields of objects that are used simply to
+# group values such as this aggregate. The getters/setters are there to
+# satisfy the superclass interface.
+
+from antlr4.RuleContext import RuleContext
+from antlr4.Token import Token
+from antlr4.tree.Tree import ParseTreeListener, ParseTree, TerminalNodeImpl, ErrorNodeImpl, TerminalNode, \
+ INVALID_INTERVAL
+
+# need forward declaration
+ParserRuleContext = None
+
+class ParserRuleContext(RuleContext):
+ __slots__ = ('children', 'start', 'stop', 'exception')
+ def __init__(self, parent:ParserRuleContext = None, invokingStateNumber:int = None ):
+ super().__init__(parent, invokingStateNumber)
+ #* If we are debugging or building a parse tree for a visitor,
+ # we need to track all of the tokens and rule invocations associated
+ # with this rule's context. This is empty for parsing w/o tree constr.
+ # operation because we don't the need to track the details about
+ # how we parse this rule.
+ #/
+ self.children = None
+ self.start = None
+ self.stop = None
+ # The exception that forced this rule to return. If the rule successfully
+ # completed, this is {@code null}.
+ self.exception = None
+
+ #* COPY a ctx (I'm deliberately not using copy constructor)#/
+ #
+ # This is used in the generated parser code to flip a generic XContext
+ # node for rule X to a YContext for alt label Y. In that sense, it is
+ # not really a generic copy function.
+ #
+ # If we do an error sync() at start of a rule, we might add error nodes
+ # to the generic XContext so this function must copy those nodes to
+ # the YContext as well else they are lost!
+ #/
+ def copyFrom(self, ctx:ParserRuleContext):
+ # from RuleContext
+ self.parentCtx = ctx.parentCtx
+ self.invokingState = ctx.invokingState
+ self.children = None
+ self.start = ctx.start
+ self.stop = ctx.stop
+
+ # copy any error nodes to alt label node
+ if ctx.children is not None:
+ self.children = []
+ # reset parent pointer for any error nodes
+ for child in ctx.children:
+ if isinstance(child, ErrorNodeImpl):
+ self.children.append(child)
+ child.parentCtx = self
+
+ # Double dispatch methods for listeners
+ def enterRule(self, listener:ParseTreeListener):
+ pass
+
+ def exitRule(self, listener:ParseTreeListener):
+ pass
+
+ #* Does not set parent link; other add methods do that#/
+ def addChild(self, child:ParseTree):
+ if self.children is None:
+ self.children = []
+ self.children.append(child)
+ return child
+
+ #* Used by enterOuterAlt to toss out a RuleContext previously added as
+ # we entered a rule. If we have # label, we will need to remove
+ # generic ruleContext object.
+ #/
+ def removeLastChild(self):
+ if self.children is not None:
+ del self.children[len(self.children)-1]
+
+ def addTokenNode(self, token:Token):
+ node = TerminalNodeImpl(token)
+ self.addChild(node)
+ node.parentCtx = self
+ return node
+
+ def addErrorNode(self, badToken:Token):
+ node = ErrorNodeImpl(badToken)
+ self.addChild(node)
+ node.parentCtx = self
+ return node
+
+ def getChild(self, i:int, ttype:type = None):
+ if ttype is None:
+ return self.children[i] if len(self.children)>i else None
+ else:
+ for child in self.getChildren():
+ if not isinstance(child, ttype):
+ continue
+ if i==0:
+ return child
+ i -= 1
+ return None
+
+ def getChildren(self, predicate = None):
+ if self.children is not None:
+ for child in self.children:
+ if predicate is not None and not predicate(child):
+ continue
+ yield child
+
+ def getToken(self, ttype:int, i:int):
+ for child in self.getChildren():
+ if not isinstance(child, TerminalNode):
+ continue
+ if child.symbol.type != ttype:
+ continue
+ if i==0:
+ return child
+ i -= 1
+ return None
+
+ def getTokens(self, ttype:int ):
+ if self.getChildren() is None:
+ return []
+ tokens = []
+ for child in self.getChildren():
+ if not isinstance(child, TerminalNode):
+ continue
+ if child.symbol.type != ttype:
+ continue
+ tokens.append(child)
+ return tokens
+
+ def getTypedRuleContext(self, ctxType:type, i:int):
+ return self.getChild(i, ctxType)
+
+ def getTypedRuleContexts(self, ctxType:type):
+ children = self.getChildren()
+ if children is None:
+ return []
+ contexts = []
+ for child in children:
+ if not isinstance(child, ctxType):
+ continue
+ contexts.append(child)
+ return contexts
+
+ def getChildCount(self):
+ return len(self.children) if self.children else 0
+
+ def getSourceInterval(self):
+ if self.start is None or self.stop is None:
+ return INVALID_INTERVAL
+ else:
+ return (self.start.tokenIndex, self.stop.tokenIndex)
+
+
+RuleContext.EMPTY = ParserRuleContext()
+
+class InterpreterRuleContext(ParserRuleContext):
+
+ def __init__(self, parent:ParserRuleContext, invokingStateNumber:int, ruleIndex:int):
+ super().__init__(parent, invokingStateNumber)
+ self.ruleIndex = ruleIndex
diff --git a/cameras/deps/antlr4/PredictionContext.py b/cameras/deps/antlr4/PredictionContext.py
new file mode 100644
index 0000000000000000000000000000000000000000..7cbb7e49e1c563d75059ed7b4043f7f75da68132
--- /dev/null
+++ b/cameras/deps/antlr4/PredictionContext.py
@@ -0,0 +1,623 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+from io import StringIO
+
+from antlr4.error.Errors import IllegalStateException
+
+from antlr4.RuleContext import RuleContext
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNState import ATNState
+
+
+class PredictionContext(object):
+
+ # Represents {@code $} in local context prediction, which means wildcard.
+ # {@code#+x =#}.
+ #/
+ EMPTY = None
+
+ # Represents {@code $} in an array in full context mode, when {@code $}
+ # doesn't mean wildcard: {@code $ + x = [$,x]}. Here,
+ # {@code $} = {@link #EMPTY_RETURN_STATE}.
+ #/
+ EMPTY_RETURN_STATE = 0x7FFFFFFF
+
+ globalNodeCount = 1
+ id = globalNodeCount
+
+ # Stores the computed hash code of this {@link PredictionContext}. The hash
+ # code is computed in parts to match the following reference algorithm.
+ #
+ #
+ #/
+
+ def __init__(self, cachedHashCode:int):
+ self.cachedHashCode = cachedHashCode
+
+ def __len__(self):
+ return 0
+
+ # This means only the {@link #EMPTY} context is in set.
+ def isEmpty(self):
+ return self is self.EMPTY
+
+ def hasEmptyPath(self):
+ return self.getReturnState(len(self) - 1) == self.EMPTY_RETURN_STATE
+
+ def getReturnState(self, index:int):
+ raise IllegalStateException("illegal!")
+
+ def __hash__(self):
+ return self.cachedHashCode
+
+def calculateHashCode(parent:PredictionContext, returnState:int):
+ return hash("") if parent is None else hash((hash(parent), returnState))
+
+def calculateListsHashCode(parents:[], returnStates:[] ):
+ h = 0
+ for parent, returnState in zip(parents, returnStates):
+ h = hash((h, calculateHashCode(parent, returnState)))
+ return h
+
+# Used to cache {@link PredictionContext} objects. Its used for the shared
+# context cash associated with contexts in DFA states. This cache
+# can be used for both lexers and parsers.
+
+class PredictionContextCache(object):
+
+ def __init__(self):
+ self.cache = dict()
+
+ # Add a context to the cache and return it. If the context already exists,
+ # return that one instead and do not add a new context to the cache.
+ # Protect shared cache from unsafe thread access.
+ #
+ def add(self, ctx:PredictionContext):
+ if ctx==PredictionContext.EMPTY:
+ return PredictionContext.EMPTY
+ existing = self.cache.get(ctx, None)
+ if existing is not None:
+ return existing
+ self.cache[ctx] = ctx
+ return ctx
+
+ def get(self, ctx:PredictionContext):
+ return self.cache.get(ctx, None)
+
+ def __len__(self):
+ return len(self.cache)
+
+
+class SingletonPredictionContext(PredictionContext):
+
+ @staticmethod
+ def create(parent:PredictionContext , returnState:int ):
+ if returnState == PredictionContext.EMPTY_RETURN_STATE and parent is None:
+ # someone can pass in the bits of an array ctx that mean $
+ return SingletonPredictionContext.EMPTY
+ else:
+ return SingletonPredictionContext(parent, returnState)
+
+ def __init__(self, parent:PredictionContext, returnState:int):
+ hashCode = calculateHashCode(parent, returnState)
+ super().__init__(hashCode)
+ self.parentCtx = parent
+ self.returnState = returnState
+
+ def __len__(self):
+ return 1
+
+ def getParent(self, index:int):
+ return self.parentCtx
+
+ def getReturnState(self, index:int):
+ return self.returnState
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif other is None:
+ return False
+ elif not isinstance(other, SingletonPredictionContext):
+ return False
+ else:
+ return self.returnState == other.returnState and self.parentCtx == other.parentCtx
+
+ def __hash__(self):
+ return self.cachedHashCode
+
+ def __str__(self):
+ up = "" if self.parentCtx is None else str(self.parentCtx)
+ if len(up)==0:
+ if self.returnState == self.EMPTY_RETURN_STATE:
+ return "$"
+ else:
+ return str(self.returnState)
+ else:
+ return str(self.returnState) + " " + up
+
+
+class EmptyPredictionContext(SingletonPredictionContext):
+
+ def __init__(self):
+ super().__init__(None, PredictionContext.EMPTY_RETURN_STATE)
+
+ def isEmpty(self):
+ return True
+
+ def __eq__(self, other):
+ return self is other
+
+ def __hash__(self):
+ return self.cachedHashCode
+
+ def __str__(self):
+ return "$"
+
+
+PredictionContext.EMPTY = EmptyPredictionContext()
+
+class ArrayPredictionContext(PredictionContext):
+ # Parent can be null only if full ctx mode and we make an array
+ # from {@link #EMPTY} and non-empty. We merge {@link #EMPTY} by using null parent and
+ # returnState == {@link #EMPTY_RETURN_STATE}.
+
+ def __init__(self, parents:list, returnStates:list):
+ super().__init__(calculateListsHashCode(parents, returnStates))
+ self.parents = parents
+ self.returnStates = returnStates
+
+ def isEmpty(self):
+ # since EMPTY_RETURN_STATE can only appear in the last position, we
+ # don't need to verify that size==1
+ return self.returnStates[0]==PredictionContext.EMPTY_RETURN_STATE
+
+ def __len__(self):
+ return len(self.returnStates)
+
+ def getParent(self, index:int):
+ return self.parents[index]
+
+ def getReturnState(self, index:int):
+ return self.returnStates[index]
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, ArrayPredictionContext):
+ return False
+ elif hash(self) != hash(other):
+ return False # can't be same if hash is different
+ else:
+ return self.returnStates==other.returnStates and self.parents==other.parents
+
+ def __str__(self):
+ if self.isEmpty():
+ return "[]"
+ with StringIO() as buf:
+ buf.write("[")
+ for i in range(0,len(self.returnStates)):
+ if i>0:
+ buf.write(", ")
+ if self.returnStates[i]==PredictionContext.EMPTY_RETURN_STATE:
+ buf.write("$")
+ continue
+ buf.write(str(self.returnStates[i]))
+ if self.parents[i] is not None:
+ buf.write(' ')
+ buf.write(str(self.parents[i]))
+ else:
+ buf.write("null")
+ buf.write("]")
+ return buf.getvalue()
+
+ def __hash__(self):
+ return self.cachedHashCode
+
+
+
+# Convert a {@link RuleContext} tree to a {@link PredictionContext} graph.
+# Return {@link #EMPTY} if {@code outerContext} is empty or null.
+#/
+def PredictionContextFromRuleContext(atn:ATN, outerContext:RuleContext=None):
+ if outerContext is None:
+ outerContext = RuleContext.EMPTY
+
+ # if we are in RuleContext of start rule, s, then PredictionContext
+ # is EMPTY. Nobody called us. (if we are empty, return empty)
+ if outerContext.parentCtx is None or outerContext is RuleContext.EMPTY:
+ return PredictionContext.EMPTY
+
+ # If we have a parent, convert it to a PredictionContext graph
+ parent = PredictionContextFromRuleContext(atn, outerContext.parentCtx)
+ state = atn.states[outerContext.invokingState]
+ transition = state.transitions[0]
+ return SingletonPredictionContext.create(parent, transition.followState.stateNumber)
+
+
+def merge(a:PredictionContext, b:PredictionContext, rootIsWildcard:bool, mergeCache:dict):
+
+ # share same graph if both same
+ if a==b:
+ return a
+
+ if isinstance(a, SingletonPredictionContext) and isinstance(b, SingletonPredictionContext):
+ return mergeSingletons(a, b, rootIsWildcard, mergeCache)
+
+ # At least one of a or b is array
+ # If one is $ and rootIsWildcard, return $ as# wildcard
+ if rootIsWildcard:
+ if isinstance( a, EmptyPredictionContext ):
+ return a
+ if isinstance( b, EmptyPredictionContext ):
+ return b
+
+ # convert singleton so both are arrays to normalize
+ if isinstance( a, SingletonPredictionContext ):
+ a = ArrayPredictionContext([a.parentCtx], [a.returnState])
+ if isinstance( b, SingletonPredictionContext):
+ b = ArrayPredictionContext([b.parentCtx], [b.returnState])
+ return mergeArrays(a, b, rootIsWildcard, mergeCache)
+
+
+#
+# Merge two {@link SingletonPredictionContext} instances.
+#
+#
Stack tops equal, parents merge is same; return left graph.
+#
+#
+#
Same stack top, parents differ; merge parents giving array node, then
+# remainders of those graphs. A new root node is created to point to the
+# merged parents.
+#
+#
+#
Different stack tops pointing to same parent. Make array node for the
+# root where both element in the root point to the same (original)
+# parent.
+#
+#
+#
Different stack tops pointing to different parents. Make array node for
+# the root where each element points to the corresponding original
+# parent.
+#
+#
+# @param a the first {@link SingletonPredictionContext}
+# @param b the second {@link SingletonPredictionContext}
+# @param rootIsWildcard {@code true} if this is a local-context merge,
+# otherwise false to indicate a full-context merge
+# @param mergeCache
+#/
+def mergeSingletons(a:SingletonPredictionContext, b:SingletonPredictionContext, rootIsWildcard:bool, mergeCache:dict):
+ if mergeCache is not None:
+ previous = mergeCache.get((a,b), None)
+ if previous is not None:
+ return previous
+ previous = mergeCache.get((b,a), None)
+ if previous is not None:
+ return previous
+
+ merged = mergeRoot(a, b, rootIsWildcard)
+ if merged is not None:
+ if mergeCache is not None:
+ mergeCache[(a, b)] = merged
+ return merged
+
+ if a.returnState==b.returnState:
+ parent = merge(a.parentCtx, b.parentCtx, rootIsWildcard, mergeCache)
+ # if parent is same as existing a or b parent or reduced to a parent, return it
+ if parent == a.parentCtx:
+ return a # ax + bx = ax, if a=b
+ if parent == b.parentCtx:
+ return b # ax + bx = bx, if a=b
+ # else: ax + ay = a'[x,y]
+ # merge parents x and y, giving array node with x,y then remainders
+ # of those graphs. dup a, a' points at merged array
+ # new joined parent so create new singleton pointing to it, a'
+ merged = SingletonPredictionContext.create(parent, a.returnState)
+ if mergeCache is not None:
+ mergeCache[(a, b)] = merged
+ return merged
+ else: # a != b payloads differ
+ # see if we can collapse parents due to $+x parents if local ctx
+ singleParent = None
+ if a is b or (a.parentCtx is not None and a.parentCtx==b.parentCtx): # ax + bx = [a,b]x
+ singleParent = a.parentCtx
+ if singleParent is not None: # parents are same
+ # sort payloads and use same parent
+ payloads = [ a.returnState, b.returnState ]
+ if a.returnState > b.returnState:
+ payloads = [ b.returnState, a.returnState ]
+ parents = [singleParent, singleParent]
+ merged = ArrayPredictionContext(parents, payloads)
+ if mergeCache is not None:
+ mergeCache[(a, b)] = merged
+ return merged
+ # parents differ and can't merge them. Just pack together
+ # into array; can't merge.
+ # ax + by = [ax,by]
+ payloads = [ a.returnState, b.returnState ]
+ parents = [ a.parentCtx, b.parentCtx ]
+ if a.returnState > b.returnState: # sort by payload
+ payloads = [ b.returnState, a.returnState ]
+ parents = [ b.parentCtx, a.parentCtx ]
+ merged = ArrayPredictionContext(parents, payloads)
+ if mergeCache is not None:
+ mergeCache[(a, b)] = merged
+ return merged
+
+
+#
+# Handle case where at least one of {@code a} or {@code b} is
+# {@link #EMPTY}. In the following diagrams, the symbol {@code $} is used
+# to represent {@link #EMPTY}.
+#
+#
Local-Context Merges
+#
+#
These local-context merge operations are used when {@code rootIsWildcard}
+# is true.
+#
+#
{@link #EMPTY} is superset of any graph; return {@link #EMPTY}.
+#
+#
+#
{@link #EMPTY} and anything is {@code #EMPTY}, so merged parent is
+# {@code #EMPTY}; return left graph.
+#
+#
+#
Special case of last merge if local context.
+#
+#
+#
Full-Context Merges
+#
+#
These full-context merge operations are used when {@code rootIsWildcard}
+# is false.
+#
+#
+#
+#
Must keep all contexts; {@link #EMPTY} in array is a special value (and
+# null parent).
+#
+#
+#
+#
+# @param a the first {@link SingletonPredictionContext}
+# @param b the second {@link SingletonPredictionContext}
+# @param rootIsWildcard {@code true} if this is a local-context merge,
+# otherwise false to indicate a full-context merge
+#/
+def mergeRoot(a:SingletonPredictionContext, b:SingletonPredictionContext, rootIsWildcard:bool):
+ if rootIsWildcard:
+ if a == PredictionContext.EMPTY:
+ return PredictionContext.EMPTY ## + b =#
+ if b == PredictionContext.EMPTY:
+ return PredictionContext.EMPTY # a +# =#
+ else:
+ if a == PredictionContext.EMPTY and b == PredictionContext.EMPTY:
+ return PredictionContext.EMPTY # $ + $ = $
+ elif a == PredictionContext.EMPTY: # $ + x = [$,x]
+ payloads = [ b.returnState, PredictionContext.EMPTY_RETURN_STATE ]
+ parents = [ b.parentCtx, None ]
+ return ArrayPredictionContext(parents, payloads)
+ elif b == PredictionContext.EMPTY: # x + $ = [$,x] ($ is always first if present)
+ payloads = [ a.returnState, PredictionContext.EMPTY_RETURN_STATE ]
+ parents = [ a.parentCtx, None ]
+ return ArrayPredictionContext(parents, payloads)
+ return None
+
+
+#
+# Merge two {@link ArrayPredictionContext} instances.
+#
+#
Different tops, different parents.
+#
+#
+#
Shared top, same parents.
+#
+#
+#
Shared top, different parents.
+#
+#
+#
Shared top, all shared parents.
+#
+#
+#
Equal tops, merge parents and reduce top to
+# {@link SingletonPredictionContext}.
+#
+#/
+def mergeArrays(a:ArrayPredictionContext, b:ArrayPredictionContext, rootIsWildcard:bool, mergeCache:dict):
+ if mergeCache is not None:
+ previous = mergeCache.get((a,b), None)
+ if previous is not None:
+ return previous
+ previous = mergeCache.get((b,a), None)
+ if previous is not None:
+ return previous
+
+ # merge sorted payloads a + b => M
+ i = 0 # walks a
+ j = 0 # walks b
+ k = 0 # walks target M array
+
+ mergedReturnStates = [None] * (len(a.returnStates) + len( b.returnStates))
+ mergedParents = [None] * len(mergedReturnStates)
+ # walk and merge to yield mergedParents, mergedReturnStates
+ while i ax
+ if bothDollars or ax_ax:
+ mergedParents[k] = a_parent # choose left
+ mergedReturnStates[k] = payload
+ else: # ax+ay -> a'[x,y]
+ mergedParent = merge(a_parent, b_parent, rootIsWildcard, mergeCache)
+ mergedParents[k] = mergedParent
+ mergedReturnStates[k] = payload
+ i += 1 # hop over left one as usual
+ j += 1 # but also skip one in right side since we merge
+ elif a.returnStates[i] a, copy b[j] to M
+ mergedParents[k] = b_parent
+ mergedReturnStates[k] = b.returnStates[j]
+ j += 1
+ k += 1
+
+ # copy over any payloads remaining in either array
+ if i < len(a.returnStates):
+ for p in range(i, len(a.returnStates)):
+ mergedParents[k] = a.parents[p]
+ mergedReturnStates[k] = a.returnStates[p]
+ k += 1
+ else:
+ for p in range(j, len(b.returnStates)):
+ mergedParents[k] = b.parents[p]
+ mergedReturnStates[k] = b.returnStates[p]
+ k += 1
+
+ # trim merged if we combined a few that had same stack tops
+ if k < len(mergedParents): # write index < last position; trim
+ if k == 1: # for just one merged element, return singleton top
+ merged = SingletonPredictionContext.create(mergedParents[0], mergedReturnStates[0])
+ if mergeCache is not None:
+ mergeCache[(a,b)] = merged
+ return merged
+ mergedParents = mergedParents[0:k]
+ mergedReturnStates = mergedReturnStates[0:k]
+
+ merged = ArrayPredictionContext(mergedParents, mergedReturnStates)
+
+ # if we created same array as a or b, return that instead
+ # TODO: track whether this is possible above during merge sort for speed
+ if merged==a:
+ if mergeCache is not None:
+ mergeCache[(a,b)] = a
+ return a
+ if merged==b:
+ if mergeCache is not None:
+ mergeCache[(a,b)] = b
+ return b
+ combineCommonParents(mergedParents)
+
+ if mergeCache is not None:
+ mergeCache[(a,b)] = merged
+ return merged
+
+
+#
+# Make pass over all M {@code parents}; merge any {@code equals()}
+# ones.
+#/
+def combineCommonParents(parents:list):
+ uniqueParents = dict()
+
+ for p in range(0, len(parents)):
+ parent = parents[p]
+ if uniqueParents.get(parent, None) is None:
+ uniqueParents[parent] = parent
+
+ for p in range(0, len(parents)):
+ parents[p] = uniqueParents[parents[p]]
+
+def getCachedPredictionContext(context:PredictionContext, contextCache:PredictionContextCache, visited:dict):
+ if context.isEmpty():
+ return context
+ existing = visited.get(context)
+ if existing is not None:
+ return existing
+ existing = contextCache.get(context)
+ if existing is not None:
+ visited[context] = existing
+ return existing
+ changed = False
+ parents = [None] * len(context)
+ for i in range(0, len(parents)):
+ parent = getCachedPredictionContext(context.getParent(i), contextCache, visited)
+ if changed or parent is not context.getParent(i):
+ if not changed:
+ parents = [context.getParent(j) for j in range(len(context))]
+ changed = True
+ parents[i] = parent
+ if not changed:
+ contextCache.add(context)
+ visited[context] = context
+ return context
+
+ updated = None
+ if len(parents) == 0:
+ updated = PredictionContext.EMPTY
+ elif len(parents) == 1:
+ updated = SingletonPredictionContext.create(parents[0], context.getReturnState(0))
+ else:
+ updated = ArrayPredictionContext(parents, context.returnStates)
+
+ contextCache.add(updated)
+ visited[updated] = updated
+ visited[context] = updated
+
+ return updated
+
+
+# # extra structures, but cut/paste/morphed works, so leave it.
+# # seems to do a breadth-first walk
+# public static List getAllNodes(PredictionContext context) {
+# Map visited =
+# new IdentityHashMap();
+# Deque workList = new ArrayDeque();
+# workList.add(context);
+# visited.put(context, context);
+# List nodes = new ArrayList();
+# while (!workList.isEmpty()) {
+# PredictionContext current = workList.pop();
+# nodes.add(current);
+# for (int i = 0; i < current.size(); i++) {
+# PredictionContext parent = current.getParent(i);
+# if ( parent!=null && visited.put(parent, parent) == null) {
+# workList.push(parent);
+# }
+# }
+# }
+# return nodes;
+# }
+
+# ter's recursive version of Sam's getAllNodes()
+def getAllContextNodes(context:PredictionContext, nodes:list=None, visited:dict=None):
+ if nodes is None:
+ nodes = list()
+ return getAllContextNodes(context, nodes, visited)
+ elif visited is None:
+ visited = dict()
+ return getAllContextNodes(context, nodes, visited)
+ else:
+ if context is None or visited.get(context, None) is not None:
+ return nodes
+ visited.put(context, context)
+ nodes.add(context)
+ for i in range(0, len(context)):
+ getAllContextNodes(context.getParent(i), nodes, visited)
+ return nodes
+
diff --git a/cameras/deps/antlr4/Recognizer.py b/cameras/deps/antlr4/Recognizer.py
new file mode 100644
index 0000000000000000000000000000000000000000..d87738be57ebfd87569615ef24de72e04094ecaf
--- /dev/null
+++ b/cameras/deps/antlr4/Recognizer.py
@@ -0,0 +1,147 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+from antlr4.RuleContext import RuleContext
+from antlr4.Token import Token
+from antlr4.error.ErrorListener import ProxyErrorListener, ConsoleErrorListener
+
+# need forward delcaration
+RecognitionException = None
+
+class Recognizer(object):
+ __slots__ = ('_listeners', '_interp', '_stateNumber')
+
+ tokenTypeMapCache = dict()
+ ruleIndexMapCache = dict()
+
+ def __init__(self):
+ self._listeners = [ ConsoleErrorListener.INSTANCE ]
+ self._interp = None
+ self._stateNumber = -1
+
+ def extractVersion(self, version):
+ pos = version.find(".")
+ major = version[0:pos]
+ version = version[pos+1:]
+ pos = version.find(".")
+ if pos==-1:
+ pos = version.find("-")
+ if pos==-1:
+ pos = len(version)
+ minor = version[0:pos]
+ return major, minor
+
+ def checkVersion(self, toolVersion):
+ runtimeVersion = "4.9.3"
+ rvmajor, rvminor = self.extractVersion(runtimeVersion)
+ tvmajor, tvminor = self.extractVersion(toolVersion)
+ if rvmajor!=tvmajor or rvminor!=tvminor:
+ print("ANTLR runtime and generated code versions disagree: "+runtimeVersion+"!="+toolVersion)
+
+ def addErrorListener(self, listener):
+ self._listeners.append(listener)
+
+ def removeErrorListener(self, listener):
+ self._listeners.remove(listener)
+
+ def removeErrorListeners(self):
+ self._listeners = []
+
+ def getTokenTypeMap(self):
+ tokenNames = self.getTokenNames()
+ if tokenNames is None:
+ from antlr4.error.Errors import UnsupportedOperationException
+ raise UnsupportedOperationException("The current recognizer does not provide a list of token names.")
+ result = self.tokenTypeMapCache.get(tokenNames, None)
+ if result is None:
+ result = zip( tokenNames, range(0, len(tokenNames)))
+ result["EOF"] = Token.EOF
+ self.tokenTypeMapCache[tokenNames] = result
+ return result
+
+ # Get a map from rule names to rule indexes.
+ #
+ #
Used for XPath and tree pattern compilation.
+ #
+ def getRuleIndexMap(self):
+ ruleNames = self.getRuleNames()
+ if ruleNames is None:
+ from antlr4.error.Errors import UnsupportedOperationException
+ raise UnsupportedOperationException("The current recognizer does not provide a list of rule names.")
+ result = self.ruleIndexMapCache.get(ruleNames, None)
+ if result is None:
+ result = zip( ruleNames, range(0, len(ruleNames)))
+ self.ruleIndexMapCache[ruleNames] = result
+ return result
+
+ def getTokenType(self, tokenName:str):
+ ttype = self.getTokenTypeMap().get(tokenName, None)
+ if ttype is not None:
+ return ttype
+ else:
+ return Token.INVALID_TYPE
+
+
+ # What is the error header, normally line/character position information?#
+ def getErrorHeader(self, e:RecognitionException):
+ line = e.getOffendingToken().line
+ column = e.getOffendingToken().column
+ return "line "+line+":"+column
+
+
+ # How should a token be displayed in an error message? The default
+ # is to display just the text, but during development you might
+ # want to have a lot of information spit out. Override in that case
+ # to use t.toString() (which, for CommonToken, dumps everything about
+ # the token). This is better than forcing you to override a method in
+ # your token objects because you don't have to go modify your lexer
+ # so that it creates a new Java type.
+ #
+ # @deprecated This method is not called by the ANTLR 4 Runtime. Specific
+ # implementations of {@link ANTLRErrorStrategy} may provide a similar
+ # feature when necessary. For example, see
+ # {@link DefaultErrorStrategy#getTokenErrorDisplay}.
+ #
+ def getTokenErrorDisplay(self, t:Token):
+ if t is None:
+ return ""
+ s = t.text
+ if s is None:
+ if t.type==Token.EOF:
+ s = ""
+ else:
+ s = "<" + str(t.type) + ">"
+ s = s.replace("\n","\\n")
+ s = s.replace("\r","\\r")
+ s = s.replace("\t","\\t")
+ return "'" + s + "'"
+
+ def getErrorListenerDispatch(self):
+ return ProxyErrorListener(self._listeners)
+
+ # subclass needs to override these if there are sempreds or actions
+ # that the ATN interp needs to execute
+ def sempred(self, localctx:RuleContext, ruleIndex:int, actionIndex:int):
+ return True
+
+ def precpred(self, localctx:RuleContext , precedence:int):
+ return True
+
+ @property
+ def state(self):
+ return self._stateNumber
+
+ # Indicate that the recognizer has changed internal state that is
+ # consistent with the ATN state passed in. This way we always know
+ # where we are in the ATN as the parser goes along. The rule
+ # context objects form a stack that lets us see the stack of
+ # invoking rules. Combine this and we have complete ATN
+ # configuration information.
+
+ @state.setter
+ def state(self, atnState:int):
+ self._stateNumber = atnState
+
+del RecognitionException
diff --git a/cameras/deps/antlr4/RuleContext.py b/cameras/deps/antlr4/RuleContext.py
new file mode 100644
index 0000000000000000000000000000000000000000..7812ba3b1c87cc53dd5a7e64cdd0c556f6a43822
--- /dev/null
+++ b/cameras/deps/antlr4/RuleContext.py
@@ -0,0 +1,227 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+
+# A rule context is a record of a single rule invocation. It knows
+# which context invoked it, if any. If there is no parent context, then
+# naturally the invoking state is not valid. The parent link
+# provides a chain upwards from the current rule invocation to the root
+# of the invocation tree, forming a stack. We actually carry no
+# information about the rule associated with this context (except
+# when parsing). We keep only the state number of the invoking state from
+# the ATN submachine that invoked this. Contrast this with the s
+# pointer inside ParserRuleContext that tracks the current state
+# being "executed" for the current rule.
+#
+# The parent contexts are useful for computing lookahead sets and
+# getting error information.
+#
+# These objects are used during parsing and prediction.
+# For the special case of parsers, we use the subclass
+# ParserRuleContext.
+#
+# @see ParserRuleContext
+#/
+from io import StringIO
+from antlr4.tree.Tree import RuleNode, INVALID_INTERVAL, ParseTreeVisitor
+from antlr4.tree.Trees import Trees
+
+# need forward declarations
+RuleContext = None
+Parser = None
+
+class RuleContext(RuleNode):
+ __slots__ = ('parentCtx', 'invokingState')
+ EMPTY = None
+
+ def __init__(self, parent:RuleContext=None, invokingState:int=-1):
+ super().__init__()
+ # What context invoked this rule?
+ self.parentCtx = parent
+ # What state invoked the rule associated with this context?
+ # The "return address" is the followState of invokingState
+ # If parent is null, this should be -1.
+ self.invokingState = invokingState
+
+
+ def depth(self):
+ n = 0
+ p = self
+ while p is not None:
+ p = p.parentCtx
+ n += 1
+ return n
+
+ # A context is empty if there is no invoking state; meaning nobody call
+ # current context.
+ def isEmpty(self):
+ return self.invokingState == -1
+
+ # satisfy the ParseTree / SyntaxTree interface
+
+ def getSourceInterval(self):
+ return INVALID_INTERVAL
+
+ def getRuleContext(self):
+ return self
+
+ def getPayload(self):
+ return self
+
+ # Return the combined text of all child nodes. This method only considers
+ # tokens which have been added to the parse tree.
+ #
+ # Since tokens on hidden channels (e.g. whitespace or comments) are not
+ # added to the parse trees, they will not appear in the output of this
+ # method.
+ #/
+ def getText(self):
+ if self.getChildCount() == 0:
+ return ""
+ with StringIO() as builder:
+ for child in self.getChildren():
+ builder.write(child.getText())
+ return builder.getvalue()
+
+ def getRuleIndex(self):
+ return -1
+
+ # For rule associated with this parse tree internal node, return
+ # the outer alternative number used to match the input. Default
+ # implementation does not compute nor store this alt num. Create
+ # a subclass of ParserRuleContext with backing field and set
+ # option contextSuperClass.
+ # to set it.
+ def getAltNumber(self):
+ return 0 # should use ATN.INVALID_ALT_NUMBER but won't compile
+
+ # Set the outer alternative number for this context node. Default
+ # implementation does nothing to avoid backing field overhead for
+ # trees that don't need it. Create
+ # a subclass of ParserRuleContext with backing field and set
+ # option contextSuperClass.
+ def setAltNumber(self, altNumber:int):
+ pass
+
+ def getChild(self, i:int):
+ return None
+
+ def getChildCount(self):
+ return 0
+
+ def getChildren(self):
+ for c in []:
+ yield c
+
+ def accept(self, visitor:ParseTreeVisitor):
+ return visitor.visitChildren(self)
+
+ # # Call this method to view a parse tree in a dialog box visually.#/
+ # public Future inspect(@Nullable Parser parser) {
+ # List ruleNames = parser != null ? Arrays.asList(parser.getRuleNames()) : null;
+ # return inspect(ruleNames);
+ # }
+ #
+ # public Future inspect(@Nullable List ruleNames) {
+ # TreeViewer viewer = new TreeViewer(ruleNames, this);
+ # return viewer.open();
+ # }
+ #
+ # # Save this tree in a postscript file#/
+ # public void save(@Nullable Parser parser, String fileName)
+ # throws IOException, PrintException
+ # {
+ # List ruleNames = parser != null ? Arrays.asList(parser.getRuleNames()) : null;
+ # save(ruleNames, fileName);
+ # }
+ #
+ # # Save this tree in a postscript file using a particular font name and size#/
+ # public void save(@Nullable Parser parser, String fileName,
+ # String fontName, int fontSize)
+ # throws IOException
+ # {
+ # List ruleNames = parser != null ? Arrays.asList(parser.getRuleNames()) : null;
+ # save(ruleNames, fileName, fontName, fontSize);
+ # }
+ #
+ # # Save this tree in a postscript file#/
+ # public void save(@Nullable List ruleNames, String fileName)
+ # throws IOException, PrintException
+ # {
+ # Trees.writePS(this, ruleNames, fileName);
+ # }
+ #
+ # # Save this tree in a postscript file using a particular font name and size#/
+ # public void save(@Nullable List ruleNames, String fileName,
+ # String fontName, int fontSize)
+ # throws IOException
+ # {
+ # Trees.writePS(this, ruleNames, fileName, fontName, fontSize);
+ # }
+ #
+ # # Print out a whole tree, not just a node, in LISP format
+ # # (root child1 .. childN). Print just a node if this is a leaf.
+ # # We have to know the recognizer so we can get rule names.
+ # #/
+ # @Override
+ # public String toStringTree(@Nullable Parser recog) {
+ # return Trees.toStringTree(this, recog);
+ # }
+ #
+ # Print out a whole tree, not just a node, in LISP format
+ # (root child1 .. childN). Print just a node if this is a leaf.
+ #
+ def toStringTree(self, ruleNames:list=None, recog:Parser=None):
+ return Trees.toStringTree(self, ruleNames=ruleNames, recog=recog)
+ # }
+ #
+ # @Override
+ # public String toStringTree() {
+ # return toStringTree((List)null);
+ # }
+ #
+ def __str__(self):
+ return self.toString(None, None)
+
+ # @Override
+ # public String toString() {
+ # return toString((List)null, (RuleContext)null);
+ # }
+ #
+ # public final String toString(@Nullable Recognizer,?> recog) {
+ # return toString(recog, ParserRuleContext.EMPTY);
+ # }
+ #
+ # public final String toString(@Nullable List ruleNames) {
+ # return toString(ruleNames, null);
+ # }
+ #
+ # // recog null unless ParserRuleContext, in which case we use subclass toString(...)
+ # public String toString(@Nullable Recognizer,?> recog, @Nullable RuleContext stop) {
+ # String[] ruleNames = recog != null ? recog.getRuleNames() : null;
+ # List ruleNamesList = ruleNames != null ? Arrays.asList(ruleNames) : null;
+ # return toString(ruleNamesList, stop);
+ # }
+
+ def toString(self, ruleNames:list, stop:RuleContext)->str:
+ with StringIO() as buf:
+ p = self
+ buf.write("[")
+ while p is not None and p is not stop:
+ if ruleNames is None:
+ if not p.isEmpty():
+ buf.write(str(p.invokingState))
+ else:
+ ri = p.getRuleIndex()
+ ruleName = ruleNames[ri] if ri >= 0 and ri < len(ruleNames) else str(ri)
+ buf.write(ruleName)
+
+ if p.parentCtx is not None and (ruleNames is not None or not p.parentCtx.isEmpty()):
+ buf.write(" ")
+
+ p = p.parentCtx
+
+ buf.write("]")
+ return buf.getvalue()
diff --git a/cameras/deps/antlr4/StdinStream.py b/cameras/deps/antlr4/StdinStream.py
new file mode 100644
index 0000000000000000000000000000000000000000..f044fc4d770b4bc86cce2a5578f2c2fa00fc7602
--- /dev/null
+++ b/cameras/deps/antlr4/StdinStream.py
@@ -0,0 +1,11 @@
+import codecs
+import sys
+
+from antlr4.InputStream import InputStream
+
+
+class StdinStream(InputStream):
+ def __init__(self, encoding:str='ascii', errors:str='strict') -> None:
+ bytes = sys.stdin.buffer.read()
+ data = codecs.decode(bytes, encoding, errors)
+ super().__init__(data)
diff --git a/cameras/deps/antlr4/Token.py b/cameras/deps/antlr4/Token.py
new file mode 100644
index 0000000000000000000000000000000000000000..10a68a8c2260e47838a1c9403def21725aecc244
--- /dev/null
+++ b/cameras/deps/antlr4/Token.py
@@ -0,0 +1,155 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+# A token has properties: text, type, line, character position in the line
+# (so we can ignore tabs), token channel, index, and source from which
+# we obtained this token.
+from io import StringIO
+
+
+class Token (object):
+ __slots__ = ('source', 'type', 'channel', 'start', 'stop', 'tokenIndex', 'line', 'column', '_text')
+
+ INVALID_TYPE = 0
+
+ # During lookahead operations, this "token" signifies we hit rule end ATN state
+ # and did not follow it despite needing to.
+ EPSILON = -2
+
+ MIN_USER_TOKEN_TYPE = 1
+
+ EOF = -1
+
+ # All tokens go to the parser (unless skip() is called in that rule)
+ # on a particular "channel". The parser tunes to a particular channel
+ # so that whitespace etc... can go to the parser on a "hidden" channel.
+
+ DEFAULT_CHANNEL = 0
+
+ # Anything on different channel than DEFAULT_CHANNEL is not parsed
+ # by parser.
+
+ HIDDEN_CHANNEL = 1
+
+ def __init__(self):
+ self.source = None
+ self.type = None # token type of the token
+ self.channel = None # The parser ignores everything not on DEFAULT_CHANNEL
+ self.start = None # optional; return -1 if not implemented.
+ self.stop = None # optional; return -1 if not implemented.
+ self.tokenIndex = None # from 0..n-1 of the token object in the input stream
+ self.line = None # line=1..n of the 1st character
+ self.column = None # beginning of the line at which it occurs, 0..n-1
+ self._text = None # text of the token.
+
+ @property
+ def text(self):
+ return self._text
+
+ # Explicitly set the text for this token. If {code text} is not
+ # {@code null}, then {@link #getText} will return this value rather than
+ # extracting the text from the input.
+ #
+ # @param text The explicit text of the token, or {@code null} if the text
+ # should be obtained from the input along with the start and stop indexes
+ # of the token.
+
+ @text.setter
+ def text(self, text:str):
+ self._text = text
+
+
+ def getTokenSource(self):
+ return self.source[0]
+
+ def getInputStream(self):
+ return self.source[1]
+
+class CommonToken(Token):
+
+ # An empty {@link Pair} which is used as the default value of
+ # {@link #source} for tokens that do not have a source.
+ EMPTY_SOURCE = (None, None)
+
+ def __init__(self, source:tuple = EMPTY_SOURCE, type:int = None, channel:int=Token.DEFAULT_CHANNEL, start:int=-1, stop:int=-1):
+ super().__init__()
+ self.source = source
+ self.type = type
+ self.channel = channel
+ self.start = start
+ self.stop = stop
+ self.tokenIndex = -1
+ if source[0] is not None:
+ self.line = source[0].line
+ self.column = source[0].column
+ else:
+ self.column = -1
+
+ # Constructs a new {@link CommonToken} as a copy of another {@link Token}.
+ #
+ #
+ # If {@code oldToken} is also a {@link CommonToken} instance, the newly
+ # constructed token will share a reference to the {@link #text} field and
+ # the {@link Pair} stored in {@link #source}. Otherwise, {@link #text} will
+ # be assigned the result of calling {@link #getText}, and {@link #source}
+ # will be constructed from the result of {@link Token#getTokenSource} and
+ # {@link Token#getInputStream}.
+ #
+ # @param oldToken The token to copy.
+ #
+ def clone(self):
+ t = CommonToken(self.source, self.type, self.channel, self.start, self.stop)
+ t.tokenIndex = self.tokenIndex
+ t.line = self.line
+ t.column = self.column
+ t.text = self.text
+ return t
+
+ @property
+ def text(self):
+ if self._text is not None:
+ return self._text
+ input = self.getInputStream()
+ if input is None:
+ return None
+ n = input.size
+ if self.start < n and self.stop < n:
+ return input.getText(self.start, self.stop)
+ else:
+ return ""
+
+ @text.setter
+ def text(self, text:str):
+ self._text = text
+
+ def __str__(self):
+ with StringIO() as buf:
+ buf.write("[@")
+ buf.write(str(self.tokenIndex))
+ buf.write(",")
+ buf.write(str(self.start))
+ buf.write(":")
+ buf.write(str(self.stop))
+ buf.write("='")
+ txt = self.text
+ if txt is not None:
+ txt = txt.replace("\n","\\n")
+ txt = txt.replace("\r","\\r")
+ txt = txt.replace("\t","\\t")
+ else:
+ txt = ""
+ buf.write(txt)
+ buf.write("',<")
+ buf.write(str(self.type))
+ buf.write(">")
+ if self.channel > 0:
+ buf.write(",channel=")
+ buf.write(str(self.channel))
+ buf.write(",")
+ buf.write(str(self.line))
+ buf.write(":")
+ buf.write(str(self.column))
+ buf.write("]")
+ return buf.getvalue()
diff --git a/cameras/deps/antlr4/TokenStreamRewriter.py b/cameras/deps/antlr4/TokenStreamRewriter.py
new file mode 100644
index 0000000000000000000000000000000000000000..59baf8f47a1ba447a143842c8df825ef58035918
--- /dev/null
+++ b/cameras/deps/antlr4/TokenStreamRewriter.py
@@ -0,0 +1,255 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+from io import StringIO
+from antlr4.Token import Token
+
+from antlr4.CommonTokenStream import CommonTokenStream
+
+
+class TokenStreamRewriter(object):
+ __slots__ = ('tokens', 'programs', 'lastRewriteTokenIndexes')
+
+ DEFAULT_PROGRAM_NAME = "default"
+ PROGRAM_INIT_SIZE = 100
+ MIN_TOKEN_INDEX = 0
+
+ def __init__(self, tokens):
+ """
+ :type tokens: antlr4.BufferedTokenStream.BufferedTokenStream
+ :param tokens:
+ :return:
+ """
+ super(TokenStreamRewriter, self).__init__()
+ self.tokens = tokens
+ self.programs = {self.DEFAULT_PROGRAM_NAME: []}
+ self.lastRewriteTokenIndexes = {}
+
+ def getTokenStream(self):
+ return self.tokens
+
+ def rollback(self, instruction_index, program_name):
+ ins = self.programs.get(program_name, None)
+ if ins:
+ self.programs[program_name] = ins[self.MIN_TOKEN_INDEX: instruction_index]
+
+ def deleteProgram(self, program_name=DEFAULT_PROGRAM_NAME):
+ self.rollback(self.MIN_TOKEN_INDEX, program_name)
+
+ def insertAfterToken(self, token, text, program_name=DEFAULT_PROGRAM_NAME):
+ self.insertAfter(token.tokenIndex, text, program_name)
+
+ def insertAfter(self, index, text, program_name=DEFAULT_PROGRAM_NAME):
+ op = self.InsertAfterOp(self.tokens, index + 1, text)
+ rewrites = self.getProgram(program_name)
+ op.instructionIndex = len(rewrites)
+ rewrites.append(op)
+
+ def insertBeforeIndex(self, index, text):
+ self.insertBefore(self.DEFAULT_PROGRAM_NAME, index, text)
+
+ def insertBeforeToken(self, token, text, program_name=DEFAULT_PROGRAM_NAME):
+ self.insertBefore(program_name, token.tokenIndex, text)
+
+ def insertBefore(self, program_name, index, text):
+ op = self.InsertBeforeOp(self.tokens, index, text)
+ rewrites = self.getProgram(program_name)
+ op.instructionIndex = len(rewrites)
+ rewrites.append(op)
+
+ def replaceIndex(self, index, text):
+ self.replace(self.DEFAULT_PROGRAM_NAME, index, index, text)
+
+ def replaceRange(self, from_idx, to_idx, text):
+ self.replace(self.DEFAULT_PROGRAM_NAME, from_idx, to_idx, text)
+
+ def replaceSingleToken(self, token, text):
+ self.replace(self.DEFAULT_PROGRAM_NAME, token.tokenIndex, token.tokenIndex, text)
+
+ def replaceRangeTokens(self, from_token, to_token, text, program_name=DEFAULT_PROGRAM_NAME):
+ self.replace(program_name, from_token.tokenIndex, to_token.tokenIndex, text)
+
+ def replace(self, program_name, from_idx, to_idx, text):
+ if any((from_idx > to_idx, from_idx < 0, to_idx < 0, to_idx >= len(self.tokens.tokens))):
+ raise ValueError(
+ 'replace: range invalid: {}..{}(size={})'.format(from_idx, to_idx, len(self.tokens.tokens)))
+ op = self.ReplaceOp(from_idx, to_idx, self.tokens, text)
+ rewrites = self.getProgram(program_name)
+ op.instructionIndex = len(rewrites)
+ rewrites.append(op)
+
+ def deleteToken(self, token):
+ self.delete(self.DEFAULT_PROGRAM_NAME, token, token)
+
+ def deleteIndex(self, index):
+ self.delete(self.DEFAULT_PROGRAM_NAME, index, index)
+
+ def delete(self, program_name, from_idx, to_idx):
+ if isinstance(from_idx, Token):
+ self.replace(program_name, from_idx.tokenIndex, to_idx.tokenIndex, "")
+ else:
+ self.replace(program_name, from_idx, to_idx, "")
+
+ def lastRewriteTokenIndex(self, program_name=DEFAULT_PROGRAM_NAME):
+ return self.lastRewriteTokenIndexes.get(program_name, -1)
+
+ def setLastRewriteTokenIndex(self, program_name, i):
+ self.lastRewriteTokenIndexes[program_name] = i
+
+ def getProgram(self, program_name):
+ return self.programs.setdefault(program_name, [])
+
+ def getDefaultText(self):
+ return self.getText(self.DEFAULT_PROGRAM_NAME, 0, len(self.tokens.tokens) - 1)
+
+ def getText(self, program_name, start:int, stop:int):
+ """
+ :return: the text in tokens[start, stop](closed interval)
+ """
+ rewrites = self.programs.get(program_name)
+
+ # ensure start/end are in range
+ if stop > len(self.tokens.tokens) - 1:
+ stop = len(self.tokens.tokens) - 1
+ if start < 0:
+ start = 0
+
+ # if no instructions to execute
+ if not rewrites: return self.tokens.getText(start, stop)
+ buf = StringIO()
+ indexToOp = self._reduceToSingleOperationPerIndex(rewrites)
+ i = start
+ while all((i <= stop, i < len(self.tokens.tokens))):
+ op = indexToOp.pop(i, None)
+ token = self.tokens.get(i)
+ if op is None:
+ if token.type != Token.EOF: buf.write(token.text)
+ i += 1
+ else:
+ i = op.execute(buf)
+
+ if stop == len(self.tokens.tokens)-1:
+ for op in indexToOp.values():
+ if op.index >= len(self.tokens.tokens)-1: buf.write(op.text)
+
+ return buf.getvalue()
+
+ def _reduceToSingleOperationPerIndex(self, rewrites):
+ # Walk replaces
+ for i, rop in enumerate(rewrites):
+ if any((rop is None, not isinstance(rop, TokenStreamRewriter.ReplaceOp))):
+ continue
+ # Wipe prior inserts within range
+ inserts = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.InsertBeforeOp)]
+ for iop in inserts:
+ if iop.index == rop.index:
+ rewrites[iop.instructionIndex] = None
+ rop.text = '{}{}'.format(iop.text, rop.text)
+ elif all((iop.index > rop.index, iop.index <= rop.last_index)):
+ rewrites[iop.instructionIndex] = None
+
+ # Drop any prior replaces contained within
+ prevReplaces = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.ReplaceOp)]
+ for prevRop in prevReplaces:
+ if all((prevRop.index >= rop.index, prevRop.last_index <= rop.last_index)):
+ rewrites[prevRop.instructionIndex] = None
+ continue
+ isDisjoint = any((prevRop.last_indexrop.last_index))
+ if all((prevRop.text is None, rop.text is None, not isDisjoint)):
+ rewrites[prevRop.instructionIndex] = None
+ rop.index = min(prevRop.index, rop.index)
+ rop.last_index = min(prevRop.last_index, rop.last_index)
+ print('New rop {}'.format(rop))
+ elif (not(isDisjoint)):
+ raise ValueError("replace op boundaries of {} overlap with previous {}".format(rop, prevRop))
+
+ # Walk inserts
+ for i, iop in enumerate(rewrites):
+ if any((iop is None, not isinstance(iop, TokenStreamRewriter.InsertBeforeOp))):
+ continue
+ prevInserts = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.InsertBeforeOp)]
+ for prev_index, prevIop in enumerate(prevInserts):
+ if prevIop.index == iop.index and type(prevIop) is TokenStreamRewriter.InsertBeforeOp:
+ iop.text += prevIop.text
+ rewrites[prev_index] = None
+ elif prevIop.index == iop.index and type(prevIop) is TokenStreamRewriter.InsertAfterOp:
+ iop.text = prevIop.text + iop.text
+ rewrites[prev_index] = None
+ # look for replaces where iop.index is in range; error
+ prevReplaces = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.ReplaceOp)]
+ for rop in prevReplaces:
+ if iop.index == rop.index:
+ rop.text = iop.text + rop.text
+ rewrites[i] = None
+ continue
+ if all((iop.index >= rop.index, iop.index <= rop.last_index)):
+ raise ValueError("insert op {} within boundaries of previous {}".format(iop, rop))
+
+ reduced = {}
+ for i, op in enumerate(rewrites):
+ if op is None: continue
+ if reduced.get(op.index): raise ValueError('should be only one op per index')
+ reduced[op.index] = op
+
+ return reduced
+
+ class RewriteOperation(object):
+ __slots__ = ('tokens', 'index', 'text', 'instructionIndex')
+
+ def __init__(self, tokens, index, text=""):
+ """
+ :type tokens: CommonTokenStream
+ :param tokens:
+ :param index:
+ :param text:
+ :return:
+ """
+ self.tokens = tokens
+ self.index = index
+ self.text = text
+ self.instructionIndex = 0
+
+ def execute(self, buf):
+ """
+ :type buf: StringIO.StringIO
+ :param buf:
+ :return:
+ """
+ return self.index
+
+ def __str__(self):
+ return '<{}@{}:"{}">'.format(self.__class__.__name__, self.tokens.get(self.index), self.text)
+
+ class InsertBeforeOp(RewriteOperation):
+
+ def __init__(self, tokens, index, text=""):
+ super(TokenStreamRewriter.InsertBeforeOp, self).__init__(tokens, index, text)
+
+ def execute(self, buf):
+ buf.write(self.text)
+ if self.tokens.get(self.index).type != Token.EOF:
+ buf.write(self.tokens.get(self.index).text)
+ return self.index + 1
+
+ class InsertAfterOp(InsertBeforeOp):
+ pass
+
+ class ReplaceOp(RewriteOperation):
+ __slots__ = 'last_index'
+
+ def __init__(self, from_idx, to_idx, tokens, text):
+ super(TokenStreamRewriter.ReplaceOp, self).__init__(tokens, from_idx, text)
+ self.last_index = to_idx
+
+ def execute(self, buf):
+ if self.text:
+ buf.write(self.text)
+ return self.last_index + 1
+
+ def __str__(self):
+ if self.text:
+ return ''.format(self.tokens.get(self.index), self.tokens.get(self.last_index),
+ self.text)
diff --git a/cameras/deps/antlr4/Utils.py b/cameras/deps/antlr4/Utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..88c870dae63ef4bda74e25aea47befb75be34a5d
--- /dev/null
+++ b/cameras/deps/antlr4/Utils.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+from io import StringIO
+
+def str_list(val):
+ with StringIO() as buf:
+ buf.write('[')
+ first = True
+ for item in val:
+ if not first:
+ buf.write(', ')
+ buf.write(str(item))
+ first = False
+ buf.write(']')
+ return buf.getvalue()
+
+def escapeWhitespace(s:str, escapeSpaces:bool):
+ with StringIO() as buf:
+ for c in s:
+ if c==' ' and escapeSpaces:
+ buf.write('\u00B7')
+ elif c=='\t':
+ buf.write("\\t")
+ elif c=='\n':
+ buf.write("\\n")
+ elif c=='\r':
+ buf.write("\\r")
+ else:
+ buf.write(c)
+ return buf.getvalue()
diff --git a/cameras/deps/antlr4/__init__.py b/cameras/deps/antlr4/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..42027289e7af293a297b538f07f43ca4a566ef62
--- /dev/null
+++ b/cameras/deps/antlr4/__init__.py
@@ -0,0 +1,21 @@
+from antlr4.Token import Token
+from antlr4.InputStream import InputStream
+from antlr4.FileStream import FileStream
+from antlr4.StdinStream import StdinStream
+from antlr4.BufferedTokenStream import TokenStream
+from antlr4.CommonTokenStream import CommonTokenStream
+from antlr4.Lexer import Lexer
+from antlr4.Parser import Parser
+from antlr4.dfa.DFA import DFA
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNDeserializer import ATNDeserializer
+from antlr4.atn.LexerATNSimulator import LexerATNSimulator
+from antlr4.atn.ParserATNSimulator import ParserATNSimulator
+from antlr4.atn.PredictionMode import PredictionMode
+from antlr4.PredictionContext import PredictionContextCache
+from antlr4.ParserRuleContext import RuleContext, ParserRuleContext
+from antlr4.tree.Tree import ParseTreeListener, ParseTreeVisitor, ParseTreeWalker, TerminalNode, ErrorNode, RuleNode
+from antlr4.error.Errors import RecognitionException, IllegalStateException, NoViableAltException
+from antlr4.error.ErrorStrategy import BailErrorStrategy
+from antlr4.error.DiagnosticErrorListener import DiagnosticErrorListener
+from antlr4.Utils import str_list
diff --git a/cameras/deps/antlr4/atn/ATN.py b/cameras/deps/antlr4/atn/ATN.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f1abe0a4a7faacde5140d5631dfe48a79325cd4
--- /dev/null
+++ b/cameras/deps/antlr4/atn/ATN.py
@@ -0,0 +1,132 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+from antlr4.IntervalSet import IntervalSet
+
+from antlr4.RuleContext import RuleContext
+
+from antlr4.Token import Token
+from antlr4.atn.ATNType import ATNType
+from antlr4.atn.ATNState import ATNState, DecisionState
+
+
+class ATN(object):
+ __slots__ = (
+ 'grammarType', 'maxTokenType', 'states', 'decisionToState',
+ 'ruleToStartState', 'ruleToStopState', 'modeNameToStartState',
+ 'ruleToTokenType', 'lexerActions', 'modeToStartState'
+ )
+
+ INVALID_ALT_NUMBER = 0
+
+ # Used for runtime deserialization of ATNs from strings#/
+ def __init__(self, grammarType:ATNType , maxTokenType:int ):
+ # The type of the ATN.
+ self.grammarType = grammarType
+ # The maximum value for any symbol recognized by a transition in the ATN.
+ self.maxTokenType = maxTokenType
+ self.states = []
+ # Each subrule/rule is a decision point and we must track them so we
+ # can go back later and build DFA predictors for them. This includes
+ # all the rules, subrules, optional blocks, ()+, ()* etc...
+ self.decisionToState = []
+ # Maps from rule index to starting state number.
+ self.ruleToStartState = []
+ # Maps from rule index to stop state number.
+ self.ruleToStopState = None
+ self.modeNameToStartState = dict()
+ # For lexer ATNs, this maps the rule index to the resulting token type.
+ # For parser ATNs, this maps the rule index to the generated bypass token
+ # type if the
+ # {@link ATNDeserializationOptions#isGenerateRuleBypassTransitions}
+ # deserialization option was specified; otherwise, this is {@code null}.
+ self.ruleToTokenType = None
+ # For lexer ATNs, this is an array of {@link LexerAction} objects which may
+ # be referenced by action transitions in the ATN.
+ self.lexerActions = None
+ self.modeToStartState = []
+
+ # Compute the set of valid tokens that can occur starting in state {@code s}.
+ # If {@code ctx} is null, the set of tokens will not include what can follow
+ # the rule surrounding {@code s}. In other words, the set will be
+ # restricted to tokens reachable staying within {@code s}'s rule.
+ def nextTokensInContext(self, s:ATNState, ctx:RuleContext):
+ from antlr4.LL1Analyzer import LL1Analyzer
+ anal = LL1Analyzer(self)
+ return anal.LOOK(s, ctx=ctx)
+
+ # Compute the set of valid tokens that can occur starting in {@code s} and
+ # staying in same rule. {@link Token#EPSILON} is in set if we reach end of
+ # rule.
+ def nextTokensNoContext(self, s:ATNState):
+ if s.nextTokenWithinRule is not None:
+ return s.nextTokenWithinRule
+ s.nextTokenWithinRule = self.nextTokensInContext(s, None)
+ s.nextTokenWithinRule.readonly = True
+ return s.nextTokenWithinRule
+
+ def nextTokens(self, s:ATNState, ctx:RuleContext = None):
+ if ctx==None:
+ return self.nextTokensNoContext(s)
+ else:
+ return self.nextTokensInContext(s, ctx)
+
+ def addState(self, state:ATNState):
+ if state is not None:
+ state.atn = self
+ state.stateNumber = len(self.states)
+ self.states.append(state)
+
+ def removeState(self, state:ATNState):
+ self.states[state.stateNumber] = None # just free mem, don't shift states in list
+
+ def defineDecisionState(self, s:DecisionState):
+ self.decisionToState.append(s)
+ s.decision = len(self.decisionToState)-1
+ return s.decision
+
+ def getDecisionState(self, decision:int):
+ if len(self.decisionToState)==0:
+ return None
+ else:
+ return self.decisionToState[decision]
+
+ # Computes the set of input symbols which could follow ATN state number
+ # {@code stateNumber} in the specified full {@code context}. This method
+ # considers the complete parser context, but does not evaluate semantic
+ # predicates (i.e. all predicates encountered during the calculation are
+ # assumed true). If a path in the ATN exists from the starting state to the
+ # {@link RuleStopState} of the outermost context without matching any
+ # symbols, {@link Token#EOF} is added to the returned set.
+ #
+ #
If {@code context} is {@code null}, it is treated as
+ # {@link ParserRuleContext#EMPTY}.
+ #
+ # @param stateNumber the ATN state number
+ # @param context the full parse context
+ # @return The set of potentially valid input symbols which could follow the
+ # specified state in the specified context.
+ # @throws IllegalArgumentException if the ATN does not contain a state with
+ # number {@code stateNumber}
+ #/
+ def getExpectedTokens(self, stateNumber:int, ctx:RuleContext ):
+ if stateNumber < 0 or stateNumber >= len(self.states):
+ raise Exception("Invalid state number.")
+ s = self.states[stateNumber]
+ following = self.nextTokens(s)
+ if Token.EPSILON not in following:
+ return following
+ expected = IntervalSet()
+ expected.addSet(following)
+ expected.removeOne(Token.EPSILON)
+ while (ctx != None and ctx.invokingState >= 0 and Token.EPSILON in following):
+ invokingState = self.states[ctx.invokingState]
+ rt = invokingState.transitions[0]
+ following = self.nextTokens(rt.followState)
+ expected.addSet(following)
+ expected.removeOne(Token.EPSILON)
+ ctx = ctx.parentCtx
+ if Token.EPSILON in following:
+ expected.addOne(Token.EOF)
+ return expected
diff --git a/cameras/deps/antlr4/atn/ATNConfig.py b/cameras/deps/antlr4/atn/ATNConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..e008fb2efac45a24b6d9566170bbd227cee252b6
--- /dev/null
+++ b/cameras/deps/antlr4/atn/ATNConfig.py
@@ -0,0 +1,159 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+# A tuple: (ATN state, predicted alt, syntactic, semantic context).
+# The syntactic context is a graph-structured stack node whose
+# path(s) to the root is the rule invocation(s)
+# chain used to arrive at the state. The semantic context is
+# the tree of semantic predicates encountered before reaching
+# an ATN state.
+#/
+from io import StringIO
+from antlr4.PredictionContext import PredictionContext
+from antlr4.atn.ATNState import ATNState, DecisionState
+from antlr4.atn.LexerActionExecutor import LexerActionExecutor
+from antlr4.atn.SemanticContext import SemanticContext
+
+# need a forward declaration
+ATNConfig = None
+
+class ATNConfig(object):
+ __slots__ = (
+ 'state', 'alt', 'context', 'semanticContext', 'reachesIntoOuterContext',
+ 'precedenceFilterSuppressed'
+ )
+
+ def __init__(self, state:ATNState=None, alt:int=None, context:PredictionContext=None, semantic:SemanticContext=None, config:ATNConfig=None):
+ if config is not None:
+ if state is None:
+ state = config.state
+ if alt is None:
+ alt = config.alt
+ if context is None:
+ context = config.context
+ if semantic is None:
+ semantic = config.semanticContext
+ if semantic is None:
+ semantic = SemanticContext.NONE
+ # The ATN state associated with this configuration#/
+ self.state = state
+ # What alt (or lexer rule) is predicted by this configuration#/
+ self.alt = alt
+ # The stack of invoking states leading to the rule/states associated
+ # with this config. We track only those contexts pushed during
+ # execution of the ATN simulator.
+ self.context = context
+ self.semanticContext = semantic
+ # We cannot execute predicates dependent upon local context unless
+ # we know for sure we are in the correct context. Because there is
+ # no way to do this efficiently, we simply cannot evaluate
+ # dependent predicates unless we are in the rule that initially
+ # invokes the ATN simulator.
+ #
+ # closure() tracks the depth of how far we dip into the
+ # outer context: depth > 0. Note that it may not be totally
+ # accurate depth since I don't ever decrement. TODO: make it a boolean then
+ self.reachesIntoOuterContext = 0 if config is None else config.reachesIntoOuterContext
+ self.precedenceFilterSuppressed = False if config is None else config.precedenceFilterSuppressed
+
+ # An ATN configuration is equal to another if both have
+ # the same state, they predict the same alternative, and
+ # syntactic/semantic contexts are the same.
+ #/
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, ATNConfig):
+ return False
+ else:
+ return self.state.stateNumber==other.state.stateNumber \
+ and self.alt==other.alt \
+ and ((self.context is other.context) or (self.context==other.context)) \
+ and self.semanticContext==other.semanticContext \
+ and self.precedenceFilterSuppressed==other.precedenceFilterSuppressed
+
+ def __hash__(self):
+ return hash((self.state.stateNumber, self.alt, self.context, self.semanticContext))
+
+ def hashCodeForConfigSet(self):
+ return hash((self.state.stateNumber, self.alt, hash(self.semanticContext)))
+
+ def equalsForConfigSet(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, ATNConfig):
+ return False
+ else:
+ return self.state.stateNumber==other.state.stateNumber \
+ and self.alt==other.alt \
+ and self.semanticContext==other.semanticContext
+
+ def __str__(self):
+ with StringIO() as buf:
+ buf.write('(')
+ buf.write(str(self.state))
+ buf.write(",")
+ buf.write(str(self.alt))
+ if self.context is not None:
+ buf.write(",[")
+ buf.write(str(self.context))
+ buf.write("]")
+ if self.semanticContext is not None and self.semanticContext is not SemanticContext.NONE:
+ buf.write(",")
+ buf.write(str(self.semanticContext))
+ if self.reachesIntoOuterContext>0:
+ buf.write(",up=")
+ buf.write(str(self.reachesIntoOuterContext))
+ buf.write(')')
+ return buf.getvalue()
+
+# need a forward declaration
+LexerATNConfig = None
+
+class LexerATNConfig(ATNConfig):
+ __slots__ = ('lexerActionExecutor', 'passedThroughNonGreedyDecision')
+
+ def __init__(self, state:ATNState, alt:int=None, context:PredictionContext=None, semantic:SemanticContext=SemanticContext.NONE,
+ lexerActionExecutor:LexerActionExecutor=None, config:LexerATNConfig=None):
+ super().__init__(state=state, alt=alt, context=context, semantic=semantic, config=config)
+ if config is not None:
+ if lexerActionExecutor is None:
+ lexerActionExecutor = config.lexerActionExecutor
+ # This is the backing field for {@link #getLexerActionExecutor}.
+ self.lexerActionExecutor = lexerActionExecutor
+ self.passedThroughNonGreedyDecision = False if config is None else self.checkNonGreedyDecision(config, state)
+
+ def __hash__(self):
+ return hash((self.state.stateNumber, self.alt, self.context,
+ self.semanticContext, self.passedThroughNonGreedyDecision,
+ self.lexerActionExecutor))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerATNConfig):
+ return False
+ if self.passedThroughNonGreedyDecision != other.passedThroughNonGreedyDecision:
+ return False
+ if not(self.lexerActionExecutor == other.lexerActionExecutor):
+ return False
+ return super().__eq__(other)
+
+
+
+ def hashCodeForConfigSet(self):
+ return hash(self)
+
+
+
+ def equalsForConfigSet(self, other):
+ return self==other
+
+
+
+ def checkNonGreedyDecision(self, source:LexerATNConfig, target:ATNState):
+ return source.passedThroughNonGreedyDecision \
+ or isinstance(target, DecisionState) and target.nonGreedy
diff --git a/cameras/deps/antlr4/atn/ATNConfigSet.py b/cameras/deps/antlr4/atn/ATNConfigSet.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e9a512a682431e46a3f5b848420a762af3c7914
--- /dev/null
+++ b/cameras/deps/antlr4/atn/ATNConfigSet.py
@@ -0,0 +1,212 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+
+#
+# Specialized {@link Set}{@code <}{@link ATNConfig}{@code >} that can track
+# info about the set, with support for combining similar configurations using a
+# graph-structured stack.
+#/
+from io import StringIO
+from functools import reduce
+from antlr4.PredictionContext import PredictionContext, merge
+from antlr4.Utils import str_list
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNConfig import ATNConfig
+from antlr4.atn.SemanticContext import SemanticContext
+from antlr4.error.Errors import UnsupportedOperationException, IllegalStateException
+
+ATNSimulator = None
+
+class ATNConfigSet(object):
+ __slots__ = (
+ 'configLookup', 'fullCtx', 'readonly', 'configs', 'uniqueAlt',
+ 'conflictingAlts', 'hasSemanticContext', 'dipsIntoOuterContext',
+ 'cachedHashCode'
+ )
+
+ #
+ # The reason that we need this is because we don't want the hash map to use
+ # the standard hash code and equals. We need all configurations with the same
+ # {@code (s,i,_,semctx)} to be equal. Unfortunately, this key effectively doubles
+ # the number of objects associated with ATNConfigs. The other solution is to
+ # use a hash table that lets us specify the equals/hashcode operation.
+
+ def __init__(self, fullCtx:bool=True):
+ # All configs but hashed by (s, i, _, pi) not including context. Wiped out
+ # when we go readonly as this set becomes a DFA state.
+ self.configLookup = dict()
+ # Indicates that this configuration set is part of a full context
+ # LL prediction. It will be used to determine how to merge $. With SLL
+ # it's a wildcard whereas it is not for LL context merge.
+ self.fullCtx = fullCtx
+ # Indicates that the set of configurations is read-only. Do not
+ # allow any code to manipulate the set; DFA states will point at
+ # the sets and they must not change. This does not protect the other
+ # fields; in particular, conflictingAlts is set after
+ # we've made this readonly.
+ self.readonly = False
+ # Track the elements as they are added to the set; supports get(i)#/
+ self.configs = []
+
+ # TODO: these fields make me pretty uncomfortable but nice to pack up info together, saves recomputation
+ # TODO: can we track conflicts as they are added to save scanning configs later?
+ self.uniqueAlt = 0
+ self.conflictingAlts = None
+
+ # Used in parser and lexer. In lexer, it indicates we hit a pred
+ # while computing a closure operation. Don't make a DFA state from this.
+ self.hasSemanticContext = False
+ self.dipsIntoOuterContext = False
+
+ self.cachedHashCode = -1
+
+ def __iter__(self):
+ return self.configs.__iter__()
+
+ # Adding a new config means merging contexts with existing configs for
+ # {@code (s, i, pi, _)}, where {@code s} is the
+ # {@link ATNConfig#state}, {@code i} is the {@link ATNConfig#alt}, and
+ # {@code pi} is the {@link ATNConfig#semanticContext}. We use
+ # {@code (s,i,pi)} as key.
+ #
+ #
This method updates {@link #dipsIntoOuterContext} and
+ # {@link #hasSemanticContext} when necessary.
+ #/
+ def add(self, config:ATNConfig, mergeCache=None):
+ if self.readonly:
+ raise Exception("This set is readonly")
+ if config.semanticContext is not SemanticContext.NONE:
+ self.hasSemanticContext = True
+ if config.reachesIntoOuterContext > 0:
+ self.dipsIntoOuterContext = True
+ existing = self.getOrAdd(config)
+ if existing is config:
+ self.cachedHashCode = -1
+ self.configs.append(config) # track order here
+ return True
+ # a previous (s,i,pi,_), merge with it and save result
+ rootIsWildcard = not self.fullCtx
+ merged = merge(existing.context, config.context, rootIsWildcard, mergeCache)
+ # no need to check for existing.context, config.context in cache
+ # since only way to create new graphs is "call rule" and here.
+ # We cache at both places.
+ existing.reachesIntoOuterContext = max(existing.reachesIntoOuterContext, config.reachesIntoOuterContext)
+ # make sure to preserve the precedence filter suppression during the merge
+ if config.precedenceFilterSuppressed:
+ existing.precedenceFilterSuppressed = True
+ existing.context = merged # replace context; no need to alt mapping
+ return True
+
+ def getOrAdd(self, config:ATNConfig):
+ h = config.hashCodeForConfigSet()
+ l = self.configLookup.get(h, None)
+ if l is not None:
+ r = next((cfg for cfg in l if config.equalsForConfigSet(cfg)), None)
+ if r is not None:
+ return r
+ if l is None:
+ l = [config]
+ self.configLookup[h] = l
+ else:
+ l.append(config)
+ return config
+
+ def getStates(self):
+ return set(c.state for c in self.configs)
+
+ def getPredicates(self):
+ return list(cfg.semanticContext for cfg in self.configs if cfg.semanticContext!=SemanticContext.NONE)
+
+ def get(self, i:int):
+ return self.configs[i]
+
+ def optimizeConfigs(self, interpreter:ATNSimulator):
+ if self.readonly:
+ raise IllegalStateException("This set is readonly")
+ if len(self.configs)==0:
+ return
+ for config in self.configs:
+ config.context = interpreter.getCachedContext(config.context)
+
+ def addAll(self, coll:list):
+ for c in coll:
+ self.add(c)
+ return False
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, ATNConfigSet):
+ return False
+
+ same = self.configs is not None and \
+ self.configs==other.configs and \
+ self.fullCtx == other.fullCtx and \
+ self.uniqueAlt == other.uniqueAlt and \
+ self.conflictingAlts == other.conflictingAlts and \
+ self.hasSemanticContext == other.hasSemanticContext and \
+ self.dipsIntoOuterContext == other.dipsIntoOuterContext
+
+ return same
+
+ def __hash__(self):
+ if self.readonly:
+ if self.cachedHashCode == -1:
+ self.cachedHashCode = self.hashConfigs()
+ return self.cachedHashCode
+ return self.hashConfigs()
+
+ def hashConfigs(self):
+ return reduce(lambda h, cfg: hash((h, cfg)), self.configs, 0)
+
+ def __len__(self):
+ return len(self.configs)
+
+ def isEmpty(self):
+ return len(self.configs)==0
+
+ def __contains__(self, config):
+ if self.configLookup is None:
+ raise UnsupportedOperationException("This method is not implemented for readonly sets.")
+ h = config.hashCodeForConfigSet()
+ l = self.configLookup.get(h, None)
+ if l is not None:
+ for c in l:
+ if config.equalsForConfigSet(c):
+ return True
+ return False
+
+ def clear(self):
+ if self.readonly:
+ raise IllegalStateException("This set is readonly")
+ self.configs.clear()
+ self.cachedHashCode = -1
+ self.configLookup.clear()
+
+ def setReadonly(self, readonly:bool):
+ self.readonly = readonly
+ self.configLookup = None # can't mod, no need for lookup cache
+
+ def __str__(self):
+ with StringIO() as buf:
+ buf.write(str_list(self.configs))
+ if self.hasSemanticContext:
+ buf.write(",hasSemanticContext=")
+ buf.write(str(self.hasSemanticContext))
+ if self.uniqueAlt!=ATN.INVALID_ALT_NUMBER:
+ buf.write(",uniqueAlt=")
+ buf.write(str(self.uniqueAlt))
+ if self.conflictingAlts is not None:
+ buf.write(",conflictingAlts=")
+ buf.write(str(self.conflictingAlts))
+ if self.dipsIntoOuterContext:
+ buf.write(",dipsIntoOuterContext")
+ return buf.getvalue()
+
+
+class OrderedATNConfigSet(ATNConfigSet):
+
+ def __init__(self):
+ super().__init__()
diff --git a/cameras/deps/antlr4/atn/ATNDeserializationOptions.py b/cameras/deps/antlr4/atn/ATNDeserializationOptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..69d5437f35437874a60bcc76118f018edbf65fed
--- /dev/null
+++ b/cameras/deps/antlr4/atn/ATNDeserializationOptions.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+
+# need a forward declaration
+ATNDeserializationOptions = None
+
+class ATNDeserializationOptions(object):
+ __slots__ = ('readonly', 'verifyATN', 'generateRuleBypassTransitions')
+
+ defaultOptions = None
+
+ def __init__(self, copyFrom:ATNDeserializationOptions = None):
+ self.readonly = False
+ self.verifyATN = True if copyFrom is None else copyFrom.verifyATN
+ self.generateRuleBypassTransitions = False if copyFrom is None else copyFrom.generateRuleBypassTransitions
+
+ def __setattr__(self, key, value):
+ if key!="readonly" and self.readonly:
+ raise Exception("The object is read only.")
+ super(type(self), self).__setattr__(key,value)
+
+ATNDeserializationOptions.defaultOptions = ATNDeserializationOptions()
+ATNDeserializationOptions.defaultOptions.readonly = True
diff --git a/cameras/deps/antlr4/atn/ATNDeserializer.py b/cameras/deps/antlr4/atn/ATNDeserializer.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc100d05a43e10b91390dd47a7b05f2eff6fe23f
--- /dev/null
+++ b/cameras/deps/antlr4/atn/ATNDeserializer.py
@@ -0,0 +1,529 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+from uuid import UUID
+from io import StringIO
+from typing import Callable
+from antlr4.Token import Token
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNType import ATNType
+from antlr4.atn.ATNState import *
+from antlr4.atn.Transition import *
+from antlr4.atn.LexerAction import *
+from antlr4.atn.ATNDeserializationOptions import ATNDeserializationOptions
+
+# This is the earliest supported serialized UUID.
+BASE_SERIALIZED_UUID = UUID("AADB8D7E-AEEF-4415-AD2B-8204D6CF042E")
+
+# This UUID indicates the serialized ATN contains two sets of
+# IntervalSets, where the second set's values are encoded as
+# 32-bit integers to support the full Unicode SMP range up to U+10FFFF.
+ADDED_UNICODE_SMP = UUID("59627784-3BE5-417A-B9EB-8131A7286089")
+
+# This list contains all of the currently supported UUIDs, ordered by when
+# the feature first appeared in this branch.
+SUPPORTED_UUIDS = [ BASE_SERIALIZED_UUID, ADDED_UNICODE_SMP ]
+
+SERIALIZED_VERSION = 3
+
+# This is the current serialized UUID.
+SERIALIZED_UUID = ADDED_UNICODE_SMP
+
+class ATNDeserializer (object):
+ __slots__ = ('deserializationOptions', 'data', 'pos', 'uuid')
+
+ def __init__(self, options : ATNDeserializationOptions = None):
+ if options is None:
+ options = ATNDeserializationOptions.defaultOptions
+ self.deserializationOptions = options
+
+ # Determines if a particular serialized representation of an ATN supports
+ # a particular feature, identified by the {@link UUID} used for serializing
+ # the ATN at the time the feature was first introduced.
+ #
+ # @param feature The {@link UUID} marking the first time the feature was
+ # supported in the serialized ATN.
+ # @param actualUuid The {@link UUID} of the actual serialized ATN which is
+ # currently being deserialized.
+ # @return {@code true} if the {@code actualUuid} value represents a
+ # serialized ATN at or after the feature identified by {@code feature} was
+ # introduced; otherwise, {@code false}.
+
+ def isFeatureSupported(self, feature : UUID , actualUuid : UUID ):
+ idx1 = SUPPORTED_UUIDS.index(feature)
+ if idx1<0:
+ return False
+ idx2 = SUPPORTED_UUIDS.index(actualUuid)
+ return idx2 >= idx1
+
+ def deserialize(self, data : str):
+ self.reset(data)
+ self.checkVersion()
+ self.checkUUID()
+ atn = self.readATN()
+ self.readStates(atn)
+ self.readRules(atn)
+ self.readModes(atn)
+ sets = []
+ # First, read all sets with 16-bit Unicode code points <= U+FFFF.
+ self.readSets(atn, sets, self.readInt)
+ # Next, if the ATN was serialized with the Unicode SMP feature,
+ # deserialize sets with 32-bit arguments <= U+10FFFF.
+ if self.isFeatureSupported(ADDED_UNICODE_SMP, self.uuid):
+ self.readSets(atn, sets, self.readInt32)
+ self.readEdges(atn, sets)
+ self.readDecisions(atn)
+ self.readLexerActions(atn)
+ self.markPrecedenceDecisions(atn)
+ self.verifyATN(atn)
+ if self.deserializationOptions.generateRuleBypassTransitions \
+ and atn.grammarType == ATNType.PARSER:
+ self.generateRuleBypassTransitions(atn)
+ # re-verify after modification
+ self.verifyATN(atn)
+ return atn
+
+ def reset(self, data:str):
+ def adjust(c):
+ v = ord(c)
+ return v-2 if v>1 else v + 65533
+ temp = [ adjust(c) for c in data ]
+ # don't adjust the first value since that's the version number
+ temp[0] = ord(data[0])
+ self.data = temp
+ self.pos = 0
+
+ def checkVersion(self):
+ version = self.readInt()
+ if version != SERIALIZED_VERSION:
+ raise Exception("Could not deserialize ATN with version " + str(version) + " (expected " + str(SERIALIZED_VERSION) + ").")
+
+ def checkUUID(self):
+ uuid = self.readUUID()
+ if not uuid in SUPPORTED_UUIDS:
+ raise Exception("Could not deserialize ATN with UUID: " + str(uuid) + \
+ " (expected " + str(SERIALIZED_UUID) + " or a legacy UUID).", uuid, SERIALIZED_UUID)
+ self.uuid = uuid
+
+ def readATN(self):
+ idx = self.readInt()
+ grammarType = ATNType.fromOrdinal(idx)
+ maxTokenType = self.readInt()
+ return ATN(grammarType, maxTokenType)
+
+ def readStates(self, atn:ATN):
+ loopBackStateNumbers = []
+ endStateNumbers = []
+ nstates = self.readInt()
+ for i in range(0, nstates):
+ stype = self.readInt()
+ # ignore bad type of states
+ if stype==ATNState.INVALID_TYPE:
+ atn.addState(None)
+ continue
+ ruleIndex = self.readInt()
+ if ruleIndex == 0xFFFF:
+ ruleIndex = -1
+
+ s = self.stateFactory(stype, ruleIndex)
+ if stype == ATNState.LOOP_END: # special case
+ loopBackStateNumber = self.readInt()
+ loopBackStateNumbers.append((s, loopBackStateNumber))
+ elif isinstance(s, BlockStartState):
+ endStateNumber = self.readInt()
+ endStateNumbers.append((s, endStateNumber))
+
+ atn.addState(s)
+
+ # delay the assignment of loop back and end states until we know all the state instances have been initialized
+ for pair in loopBackStateNumbers:
+ pair[0].loopBackState = atn.states[pair[1]]
+
+ for pair in endStateNumbers:
+ pair[0].endState = atn.states[pair[1]]
+
+ numNonGreedyStates = self.readInt()
+ for i in range(0, numNonGreedyStates):
+ stateNumber = self.readInt()
+ atn.states[stateNumber].nonGreedy = True
+
+ numPrecedenceStates = self.readInt()
+ for i in range(0, numPrecedenceStates):
+ stateNumber = self.readInt()
+ atn.states[stateNumber].isPrecedenceRule = True
+
+ def readRules(self, atn:ATN):
+ nrules = self.readInt()
+ if atn.grammarType == ATNType.LEXER:
+ atn.ruleToTokenType = [0] * nrules
+
+ atn.ruleToStartState = [0] * nrules
+ for i in range(0, nrules):
+ s = self.readInt()
+ startState = atn.states[s]
+ atn.ruleToStartState[i] = startState
+ if atn.grammarType == ATNType.LEXER:
+ tokenType = self.readInt()
+ if tokenType == 0xFFFF:
+ tokenType = Token.EOF
+
+ atn.ruleToTokenType[i] = tokenType
+
+ atn.ruleToStopState = [0] * nrules
+ for state in atn.states:
+ if not isinstance(state, RuleStopState):
+ continue
+ atn.ruleToStopState[state.ruleIndex] = state
+ atn.ruleToStartState[state.ruleIndex].stopState = state
+
+ def readModes(self, atn:ATN):
+ nmodes = self.readInt()
+ for i in range(0, nmodes):
+ s = self.readInt()
+ atn.modeToStartState.append(atn.states[s])
+
+ def readSets(self, atn:ATN, sets:list, readUnicode:Callable[[], int]):
+ m = self.readInt()
+ for i in range(0, m):
+ iset = IntervalSet()
+ sets.append(iset)
+ n = self.readInt()
+ containsEof = self.readInt()
+ if containsEof!=0:
+ iset.addOne(-1)
+ for j in range(0, n):
+ i1 = readUnicode()
+ i2 = readUnicode()
+ iset.addRange(range(i1, i2 + 1)) # range upper limit is exclusive
+
+ def readEdges(self, atn:ATN, sets:list):
+ nedges = self.readInt()
+ for i in range(0, nedges):
+ src = self.readInt()
+ trg = self.readInt()
+ ttype = self.readInt()
+ arg1 = self.readInt()
+ arg2 = self.readInt()
+ arg3 = self.readInt()
+ trans = self.edgeFactory(atn, ttype, src, trg, arg1, arg2, arg3, sets)
+ srcState = atn.states[src]
+ srcState.addTransition(trans)
+
+ # edges for rule stop states can be derived, so they aren't serialized
+ for state in atn.states:
+ for i in range(0, len(state.transitions)):
+ t = state.transitions[i]
+ if not isinstance(t, RuleTransition):
+ continue
+ outermostPrecedenceReturn = -1
+ if atn.ruleToStartState[t.target.ruleIndex].isPrecedenceRule:
+ if t.precedence == 0:
+ outermostPrecedenceReturn = t.target.ruleIndex
+ trans = EpsilonTransition(t.followState, outermostPrecedenceReturn)
+ atn.ruleToStopState[t.target.ruleIndex].addTransition(trans)
+
+ for state in atn.states:
+ if isinstance(state, BlockStartState):
+ # we need to know the end state to set its start state
+ if state.endState is None:
+ raise Exception("IllegalState")
+ # block end states can only be associated to a single block start state
+ if state.endState.startState is not None:
+ raise Exception("IllegalState")
+ state.endState.startState = state
+
+ if isinstance(state, PlusLoopbackState):
+ for i in range(0, len(state.transitions)):
+ target = state.transitions[i].target
+ if isinstance(target, PlusBlockStartState):
+ target.loopBackState = state
+ elif isinstance(state, StarLoopbackState):
+ for i in range(0, len(state.transitions)):
+ target = state.transitions[i].target
+ if isinstance(target, StarLoopEntryState):
+ target.loopBackState = state
+
+ def readDecisions(self, atn:ATN):
+ ndecisions = self.readInt()
+ for i in range(0, ndecisions):
+ s = self.readInt()
+ decState = atn.states[s]
+ atn.decisionToState.append(decState)
+ decState.decision = i
+
+ def readLexerActions(self, atn:ATN):
+ if atn.grammarType == ATNType.LEXER:
+ count = self.readInt()
+ atn.lexerActions = [ None ] * count
+ for i in range(0, count):
+ actionType = self.readInt()
+ data1 = self.readInt()
+ if data1 == 0xFFFF:
+ data1 = -1
+ data2 = self.readInt()
+ if data2 == 0xFFFF:
+ data2 = -1
+ lexerAction = self.lexerActionFactory(actionType, data1, data2)
+ atn.lexerActions[i] = lexerAction
+
+ def generateRuleBypassTransitions(self, atn:ATN):
+
+ count = len(atn.ruleToStartState)
+ atn.ruleToTokenType = [ 0 ] * count
+ for i in range(0, count):
+ atn.ruleToTokenType[i] = atn.maxTokenType + i + 1
+
+ for i in range(0, count):
+ self.generateRuleBypassTransition(atn, i)
+
+ def generateRuleBypassTransition(self, atn:ATN, idx:int):
+
+ bypassStart = BasicBlockStartState()
+ bypassStart.ruleIndex = idx
+ atn.addState(bypassStart)
+
+ bypassStop = BlockEndState()
+ bypassStop.ruleIndex = idx
+ atn.addState(bypassStop)
+
+ bypassStart.endState = bypassStop
+ atn.defineDecisionState(bypassStart)
+
+ bypassStop.startState = bypassStart
+
+ excludeTransition = None
+
+ if atn.ruleToStartState[idx].isPrecedenceRule:
+ # wrap from the beginning of the rule to the StarLoopEntryState
+ endState = None
+ for state in atn.states:
+ if self.stateIsEndStateFor(state, idx):
+ endState = state
+ excludeTransition = state.loopBackState.transitions[0]
+ break
+
+ if excludeTransition is None:
+ raise Exception("Couldn't identify final state of the precedence rule prefix section.")
+
+ else:
+
+ endState = atn.ruleToStopState[idx]
+
+ # all non-excluded transitions that currently target end state need to target blockEnd instead
+ for state in atn.states:
+ for transition in state.transitions:
+ if transition == excludeTransition:
+ continue
+ if transition.target == endState:
+ transition.target = bypassStop
+
+ # all transitions leaving the rule start state need to leave blockStart instead
+ ruleToStartState = atn.ruleToStartState[idx]
+ count = len(ruleToStartState.transitions)
+ while count > 0:
+ bypassStart.addTransition(ruleToStartState.transitions[count-1])
+ del ruleToStartState.transitions[-1]
+
+ # link the new states
+ atn.ruleToStartState[idx].addTransition(EpsilonTransition(bypassStart))
+ bypassStop.addTransition(EpsilonTransition(endState))
+
+ matchState = BasicState()
+ atn.addState(matchState)
+ matchState.addTransition(AtomTransition(bypassStop, atn.ruleToTokenType[idx]))
+ bypassStart.addTransition(EpsilonTransition(matchState))
+
+
+ def stateIsEndStateFor(self, state:ATNState, idx:int):
+ if state.ruleIndex != idx:
+ return None
+ if not isinstance(state, StarLoopEntryState):
+ return None
+
+ maybeLoopEndState = state.transitions[len(state.transitions) - 1].target
+ if not isinstance(maybeLoopEndState, LoopEndState):
+ return None
+
+ if maybeLoopEndState.epsilonOnlyTransitions and \
+ isinstance(maybeLoopEndState.transitions[0].target, RuleStopState):
+ return state
+ else:
+ return None
+
+
+ #
+ # Analyze the {@link StarLoopEntryState} states in the specified ATN to set
+ # the {@link StarLoopEntryState#isPrecedenceDecision} field to the
+ # correct value.
+ #
+ # @param atn The ATN.
+ #
+ def markPrecedenceDecisions(self, atn:ATN):
+ for state in atn.states:
+ if not isinstance(state, StarLoopEntryState):
+ continue
+
+ # We analyze the ATN to determine if this ATN decision state is the
+ # decision for the closure block that determines whether a
+ # precedence rule should continue or complete.
+ #
+ if atn.ruleToStartState[state.ruleIndex].isPrecedenceRule:
+ maybeLoopEndState = state.transitions[len(state.transitions) - 1].target
+ if isinstance(maybeLoopEndState, LoopEndState):
+ if maybeLoopEndState.epsilonOnlyTransitions and \
+ isinstance(maybeLoopEndState.transitions[0].target, RuleStopState):
+ state.isPrecedenceDecision = True
+
+ def verifyATN(self, atn:ATN):
+ if not self.deserializationOptions.verifyATN:
+ return
+ # verify assumptions
+ for state in atn.states:
+ if state is None:
+ continue
+
+ self.checkCondition(state.epsilonOnlyTransitions or len(state.transitions) <= 1)
+
+ if isinstance(state, PlusBlockStartState):
+ self.checkCondition(state.loopBackState is not None)
+
+ if isinstance(state, StarLoopEntryState):
+ self.checkCondition(state.loopBackState is not None)
+ self.checkCondition(len(state.transitions) == 2)
+
+ if isinstance(state.transitions[0].target, StarBlockStartState):
+ self.checkCondition(isinstance(state.transitions[1].target, LoopEndState))
+ self.checkCondition(not state.nonGreedy)
+ elif isinstance(state.transitions[0].target, LoopEndState):
+ self.checkCondition(isinstance(state.transitions[1].target, StarBlockStartState))
+ self.checkCondition(state.nonGreedy)
+ else:
+ raise Exception("IllegalState")
+
+ if isinstance(state, StarLoopbackState):
+ self.checkCondition(len(state.transitions) == 1)
+ self.checkCondition(isinstance(state.transitions[0].target, StarLoopEntryState))
+
+ if isinstance(state, LoopEndState):
+ self.checkCondition(state.loopBackState is not None)
+
+ if isinstance(state, RuleStartState):
+ self.checkCondition(state.stopState is not None)
+
+ if isinstance(state, BlockStartState):
+ self.checkCondition(state.endState is not None)
+
+ if isinstance(state, BlockEndState):
+ self.checkCondition(state.startState is not None)
+
+ if isinstance(state, DecisionState):
+ self.checkCondition(len(state.transitions) <= 1 or state.decision >= 0)
+ else:
+ self.checkCondition(len(state.transitions) <= 1 or isinstance(state, RuleStopState))
+
+ def checkCondition(self, condition:bool, message=None):
+ if not condition:
+ if message is None:
+ message = "IllegalState"
+ raise Exception(message)
+
+ def readInt(self):
+ i = self.data[self.pos]
+ self.pos += 1
+ return i
+
+ def readInt32(self):
+ low = self.readInt()
+ high = self.readInt()
+ return low | (high << 16)
+
+ def readLong(self):
+ low = self.readInt32()
+ high = self.readInt32()
+ return (low & 0x00000000FFFFFFFF) | (high << 32)
+
+ def readUUID(self):
+ low = self.readLong()
+ high = self.readLong()
+ allBits = (low & 0xFFFFFFFFFFFFFFFF) | (high << 64)
+ return UUID(int=allBits)
+
+ edgeFactories = [ lambda args : None,
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : EpsilonTransition(target),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ RangeTransition(target, Token.EOF, arg2) if arg3 != 0 else RangeTransition(target, arg1, arg2),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ RuleTransition(atn.states[arg1], arg2, arg3, target),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ PredicateTransition(target, arg1, arg2, arg3 != 0),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ AtomTransition(target, Token.EOF) if arg3 != 0 else AtomTransition(target, arg1),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ ActionTransition(target, arg1, arg2, arg3 != 0),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ SetTransition(target, sets[arg1]),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ NotSetTransition(target, sets[arg1]),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ WildcardTransition(target),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ PrecedencePredicateTransition(target, arg1)
+ ]
+
+ def edgeFactory(self, atn:ATN, type:int, src:int, trg:int, arg1:int, arg2:int, arg3:int, sets:list):
+ target = atn.states[trg]
+ if type > len(self.edgeFactories) or self.edgeFactories[type] is None:
+ raise Exception("The specified transition type: " + str(type) + " is not valid.")
+ else:
+ return self.edgeFactories[type](atn, src, trg, arg1, arg2, arg3, sets, target)
+
+ stateFactories = [ lambda : None,
+ lambda : BasicState(),
+ lambda : RuleStartState(),
+ lambda : BasicBlockStartState(),
+ lambda : PlusBlockStartState(),
+ lambda : StarBlockStartState(),
+ lambda : TokensStartState(),
+ lambda : RuleStopState(),
+ lambda : BlockEndState(),
+ lambda : StarLoopbackState(),
+ lambda : StarLoopEntryState(),
+ lambda : PlusLoopbackState(),
+ lambda : LoopEndState()
+ ]
+
+ def stateFactory(self, type:int, ruleIndex:int):
+ if type> len(self.stateFactories) or self.stateFactories[type] is None:
+ raise Exception("The specified state type " + str(type) + " is not valid.")
+ else:
+ s = self.stateFactories[type]()
+ if s is not None:
+ s.ruleIndex = ruleIndex
+ return s
+
+ CHANNEL = 0 #The type of a {@link LexerChannelAction} action.
+ CUSTOM = 1 #The type of a {@link LexerCustomAction} action.
+ MODE = 2 #The type of a {@link LexerModeAction} action.
+ MORE = 3 #The type of a {@link LexerMoreAction} action.
+ POP_MODE = 4 #The type of a {@link LexerPopModeAction} action.
+ PUSH_MODE = 5 #The type of a {@link LexerPushModeAction} action.
+ SKIP = 6 #The type of a {@link LexerSkipAction} action.
+ TYPE = 7 #The type of a {@link LexerTypeAction} action.
+
+ actionFactories = [ lambda data1, data2: LexerChannelAction(data1),
+ lambda data1, data2: LexerCustomAction(data1, data2),
+ lambda data1, data2: LexerModeAction(data1),
+ lambda data1, data2: LexerMoreAction.INSTANCE,
+ lambda data1, data2: LexerPopModeAction.INSTANCE,
+ lambda data1, data2: LexerPushModeAction(data1),
+ lambda data1, data2: LexerSkipAction.INSTANCE,
+ lambda data1, data2: LexerTypeAction(data1)
+ ]
+
+ def lexerActionFactory(self, type:int, data1:int, data2:int):
+
+ if type > len(self.actionFactories) or self.actionFactories[type] is None:
+ raise Exception("The specified lexer action type " + str(type) + " is not valid.")
+ else:
+ return self.actionFactories[type](data1, data2)
diff --git a/cameras/deps/antlr4/atn/ATNSimulator.py b/cameras/deps/antlr4/atn/ATNSimulator.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f6f53f488fa2b731fb39ceecf350b0cb43d1c39
--- /dev/null
+++ b/cameras/deps/antlr4/atn/ATNSimulator.py
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+from antlr4.PredictionContext import PredictionContextCache, PredictionContext, getCachedPredictionContext
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.dfa.DFAState import DFAState
+
+
+class ATNSimulator(object):
+ __slots__ = ('atn', 'sharedContextCache', '__dict__')
+
+ # Must distinguish between missing edge and edge we know leads nowhere#/
+ ERROR = DFAState(configs=ATNConfigSet())
+ ERROR.stateNumber = 0x7FFFFFFF
+
+ # The context cache maps all PredictionContext objects that are ==
+ # to a single cached copy. This cache is shared across all contexts
+ # in all ATNConfigs in all DFA states. We rebuild each ATNConfigSet
+ # to use only cached nodes/graphs in addDFAState(). We don't want to
+ # fill this during closure() since there are lots of contexts that
+ # pop up but are not used ever again. It also greatly slows down closure().
+ #
+ #
This cache makes a huge difference in memory and a little bit in speed.
+ # For the Java grammar on java.*, it dropped the memory requirements
+ # at the end from 25M to 16M. We don't store any of the full context
+ # graphs in the DFA because they are limited to local context only,
+ # but apparently there's a lot of repetition there as well. We optimize
+ # the config contexts before storing the config set in the DFA states
+ # by literally rebuilding them with cached subgraphs only.
+ #
+ #
I tried a cache for use during closure operations, that was
+ # whacked after each adaptivePredict(). It cost a little bit
+ # more time I think and doesn't save on the overall footprint
+ # so it's not worth the complexity.
+ #/
+ def __init__(self, atn:ATN, sharedContextCache:PredictionContextCache):
+ self.atn = atn
+ self.sharedContextCache = sharedContextCache
+
+ def getCachedContext(self, context:PredictionContext):
+ if self.sharedContextCache is None:
+ return context
+ visited = dict()
+ return getCachedPredictionContext(context, self.sharedContextCache, visited)
diff --git a/cameras/deps/antlr4/atn/ATNState.py b/cameras/deps/antlr4/atn/ATNState.py
new file mode 100644
index 0000000000000000000000000000000000000000..fbf6a7b9442905c992bb1a88ce343a8a9d63f5b7
--- /dev/null
+++ b/cameras/deps/antlr4/atn/ATNState.py
@@ -0,0 +1,264 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+# The following images show the relation of states and
+# {@link ATNState#transitions} for various grammar constructs.
+#
+#
+#
+#
Solid edges marked with an ε indicate a required
+# {@link EpsilonTransition}.
+#
+#
Dashed edges indicate locations where any transition derived from
+# {@link Transition} might appear.
+#
+#
Dashed nodes are place holders for either a sequence of linked
+# {@link BasicState} states or the inclusion of a block representing a nested
+# construct in one of the forms below.
+#
+#
Nodes showing multiple outgoing alternatives with a {@code ...} support
+# any number of alternatives (one or more). Nodes without the {@code ...} only
+# support the exact number of alternatives shown in the diagram.
+#
+#
+#
+#
Basic Blocks
+#
+#
Rule
+#
+#
+#
+#
Block of 1 or more alternatives
+#
+#
+#
+#
Greedy Loops
+#
+#
Greedy Closure: {@code (...)*}
+#
+#
+#
+#
Greedy Positive Closure: {@code (...)+}
+#
+#
+#
+#
Greedy Optional: {@code (...)?}
+#
+#
+#
+#
Non-Greedy Loops
+#
+#
Non-Greedy Closure: {@code (...)*?}
+#
+#
+#
+#
Non-Greedy Positive Closure: {@code (...)+?}
+#
+#
+#
+#
Non-Greedy Optional: {@code (...)??}
+#
+#
+#
+
+from antlr4.atn.Transition import Transition
+
+INITIAL_NUM_TRANSITIONS = 4
+
+class ATNState(object):
+ __slots__ = (
+ 'atn', 'stateNumber', 'stateType', 'ruleIndex', 'epsilonOnlyTransitions',
+ 'transitions', 'nextTokenWithinRule',
+ )
+
+ # constants for serialization
+ INVALID_TYPE = 0
+ BASIC = 1
+ RULE_START = 2
+ BLOCK_START = 3
+ PLUS_BLOCK_START = 4
+ STAR_BLOCK_START = 5
+ TOKEN_START = 6
+ RULE_STOP = 7
+ BLOCK_END = 8
+ STAR_LOOP_BACK = 9
+ STAR_LOOP_ENTRY = 10
+ PLUS_LOOP_BACK = 11
+ LOOP_END = 12
+
+ serializationNames = [
+ "INVALID",
+ "BASIC",
+ "RULE_START",
+ "BLOCK_START",
+ "PLUS_BLOCK_START",
+ "STAR_BLOCK_START",
+ "TOKEN_START",
+ "RULE_STOP",
+ "BLOCK_END",
+ "STAR_LOOP_BACK",
+ "STAR_LOOP_ENTRY",
+ "PLUS_LOOP_BACK",
+ "LOOP_END" ]
+
+ INVALID_STATE_NUMBER = -1
+
+ def __init__(self):
+ # Which ATN are we in?
+ self.atn = None
+ self.stateNumber = ATNState.INVALID_STATE_NUMBER
+ self.stateType = None
+ self.ruleIndex = 0 # at runtime, we don't have Rule objects
+ self.epsilonOnlyTransitions = False
+ # Track the transitions emanating from this ATN state.
+ self.transitions = []
+ # Used to cache lookahead during parsing, not used during construction
+ self.nextTokenWithinRule = None
+
+ def __hash__(self):
+ return self.stateNumber
+
+ def __eq__(self, other):
+ return isinstance(other, ATNState) and self.stateNumber==other.stateNumber
+
+ def onlyHasEpsilonTransitions(self):
+ return self.epsilonOnlyTransitions
+
+ def isNonGreedyExitState(self):
+ return False
+
+ def __str__(self):
+ return str(self.stateNumber)
+
+ def addTransition(self, trans:Transition, index:int=-1):
+ if len(self.transitions)==0:
+ self.epsilonOnlyTransitions = trans.isEpsilon
+ elif self.epsilonOnlyTransitions != trans.isEpsilon:
+ self.epsilonOnlyTransitions = False
+ # TODO System.err.format(Locale.getDefault(), "ATN state %d has both epsilon and non-epsilon transitions.\n", stateNumber);
+ if index==-1:
+ self.transitions.append(trans)
+ else:
+ self.transitions.insert(index, trans)
+
+class BasicState(ATNState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.BASIC
+
+
+class DecisionState(ATNState):
+ __slots__ = ('decision', 'nonGreedy')
+ def __init__(self):
+ super().__init__()
+ self.decision = -1
+ self.nonGreedy = False
+
+# The start of a regular {@code (...)} block.
+class BlockStartState(DecisionState):
+ __slots__ = 'endState'
+
+ def __init__(self):
+ super().__init__()
+ self.endState = None
+
+class BasicBlockStartState(BlockStartState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.BLOCK_START
+
+# Terminal node of a simple {@code (a|b|c)} block.
+class BlockEndState(ATNState):
+ __slots__ = 'startState'
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.BLOCK_END
+ self.startState = None
+
+# The last node in the ATN for a rule, unless that rule is the start symbol.
+# In that case, there is one transition to EOF. Later, we might encode
+# references to all calls to this rule to compute FOLLOW sets for
+# error handling.
+#
+class RuleStopState(ATNState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.RULE_STOP
+
+class RuleStartState(ATNState):
+ __slots__ = ('stopState', 'isPrecedenceRule')
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.RULE_START
+ self.stopState = None
+ self.isPrecedenceRule = False
+
+# Decision state for {@code A+} and {@code (A|B)+}. It has two transitions:
+# one to the loop back to start of the block and one to exit.
+#
+class PlusLoopbackState(DecisionState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.PLUS_LOOP_BACK
+
+# Start of {@code (A|B|...)+} loop. Technically a decision state, but
+# we don't use for code generation; somebody might need it, so I'm defining
+# it for completeness. In reality, the {@link PlusLoopbackState} node is the
+# real decision-making note for {@code A+}.
+#
+class PlusBlockStartState(BlockStartState):
+ __slots__ = 'loopBackState'
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.PLUS_BLOCK_START
+ self.loopBackState = None
+
+# The block that begins a closure loop.
+class StarBlockStartState(BlockStartState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.STAR_BLOCK_START
+
+class StarLoopbackState(ATNState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.STAR_LOOP_BACK
+
+
+class StarLoopEntryState(DecisionState):
+ __slots__ = ('loopBackState', 'isPrecedenceDecision')
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.STAR_LOOP_ENTRY
+ self.loopBackState = None
+ # Indicates whether this state can benefit from a precedence DFA during SLL decision making.
+ self.isPrecedenceDecision = None
+
+# Mark the end of a * or + loop.
+class LoopEndState(ATNState):
+ __slots__ = 'loopBackState'
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.LOOP_END
+ self.loopBackState = None
+
+# The Tokens rule start state linking to each lexer rule start state */
+class TokensStartState(DecisionState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.TOKEN_START
diff --git a/cameras/deps/antlr4/atn/ATNType.py b/cameras/deps/antlr4/atn/ATNType.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc0d4d925c6babee1caeee08f5e32fe4a1d6d0d4
--- /dev/null
+++ b/cameras/deps/antlr4/atn/ATNType.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+from enum import IntEnum
+
+# Represents the type of recognizer an ATN applies to.
+
+class ATNType(IntEnum):
+
+ LEXER = 0
+ PARSER = 1
+
+ @classmethod
+ def fromOrdinal(cls, i:int):
+ return cls._value2member_map_[i]
diff --git a/cameras/deps/antlr4/atn/LexerATNSimulator.py b/cameras/deps/antlr4/atn/LexerATNSimulator.py
new file mode 100644
index 0000000000000000000000000000000000000000..71201ff5f989820d340aac8672ab31db8579b55c
--- /dev/null
+++ b/cameras/deps/antlr4/atn/LexerATNSimulator.py
@@ -0,0 +1,570 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+# When we hit an accept state in either the DFA or the ATN, we
+# have to notify the character stream to start buffering characters
+# via {@link IntStream#mark} and record the current state. The current sim state
+# includes the current index into the input, the current line,
+# and current character position in that line. Note that the Lexer is
+# tracking the starting line and characterization of the token. These
+# variables track the "state" of the simulator when it hits an accept state.
+#
+#
We track these variables separately for the DFA and ATN simulation
+# because the DFA simulation often has to fail over to the ATN
+# simulation. If the ATN simulation fails, we need the DFA to fall
+# back to its previously accepted state, if any. If the ATN succeeds,
+# then the ATN does the accept and the DFA simulator that invoked it
+# can simply return the predicted token type.
+#/
+
+from antlr4.PredictionContext import PredictionContextCache, SingletonPredictionContext, PredictionContext
+from antlr4.InputStream import InputStream
+from antlr4.Token import Token
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNConfig import LexerATNConfig
+from antlr4.atn.ATNSimulator import ATNSimulator
+from antlr4.atn.ATNConfigSet import ATNConfigSet, OrderedATNConfigSet
+from antlr4.atn.ATNState import RuleStopState, ATNState
+from antlr4.atn.LexerActionExecutor import LexerActionExecutor
+from antlr4.atn.Transition import Transition
+from antlr4.dfa.DFAState import DFAState
+from antlr4.error.Errors import LexerNoViableAltException, UnsupportedOperationException
+
+class SimState(object):
+ __slots__ = ('index', 'line', 'column', 'dfaState')
+
+ def __init__(self):
+ self.reset()
+
+ def reset(self):
+ self.index = -1
+ self.line = 0
+ self.column = -1
+ self.dfaState = None
+
+# need forward declaration
+Lexer = None
+LexerATNSimulator = None
+
+class LexerATNSimulator(ATNSimulator):
+ __slots__ = (
+ 'decisionToDFA', 'recog', 'startIndex', 'line', 'column', 'mode',
+ 'DEFAULT_MODE', 'MAX_CHAR_VALUE', 'prevAccept'
+ )
+
+ debug = False
+ dfa_debug = False
+
+ MIN_DFA_EDGE = 0
+ MAX_DFA_EDGE = 127 # forces unicode to stay in ATN
+
+ ERROR = None
+
+ def __init__(self, recog:Lexer, atn:ATN, decisionToDFA:list, sharedContextCache:PredictionContextCache):
+ super().__init__(atn, sharedContextCache)
+ self.decisionToDFA = decisionToDFA
+ self.recog = recog
+ # The current token's starting index into the character stream.
+ # Shared across DFA to ATN simulation in case the ATN fails and the
+ # DFA did not have a previous accept state. In this case, we use the
+ # ATN-generated exception object.
+ self.startIndex = -1
+ # line number 1..n within the input#/
+ self.line = 1
+ # The index of the character relative to the beginning of the line 0..n-1#/
+ self.column = 0
+ from antlr4.Lexer import Lexer
+ self.mode = Lexer.DEFAULT_MODE
+ # Cache Lexer properties to avoid further imports
+ self.DEFAULT_MODE = Lexer.DEFAULT_MODE
+ self.MAX_CHAR_VALUE = Lexer.MAX_CHAR_VALUE
+ # Used during DFA/ATN exec to record the most recent accept configuration info
+ self.prevAccept = SimState()
+
+
+ def copyState(self, simulator:LexerATNSimulator ):
+ self.column = simulator.column
+ self.line = simulator.line
+ self.mode = simulator.mode
+ self.startIndex = simulator.startIndex
+
+ def match(self, input:InputStream , mode:int):
+ self.mode = mode
+ mark = input.mark()
+ try:
+ self.startIndex = input.index
+ self.prevAccept.reset()
+ dfa = self.decisionToDFA[mode]
+ if dfa.s0 is None:
+ return self.matchATN(input)
+ else:
+ return self.execATN(input, dfa.s0)
+ finally:
+ input.release(mark)
+
+ def reset(self):
+ self.prevAccept.reset()
+ self.startIndex = -1
+ self.line = 1
+ self.column = 0
+ self.mode = self.DEFAULT_MODE
+
+ def matchATN(self, input:InputStream):
+ startState = self.atn.modeToStartState[self.mode]
+
+ if LexerATNSimulator.debug:
+ print("matchATN mode " + str(self.mode) + " start: " + str(startState))
+
+ old_mode = self.mode
+ s0_closure = self.computeStartState(input, startState)
+ suppressEdge = s0_closure.hasSemanticContext
+ s0_closure.hasSemanticContext = False
+
+ next = self.addDFAState(s0_closure)
+ if not suppressEdge:
+ self.decisionToDFA[self.mode].s0 = next
+
+ predict = self.execATN(input, next)
+
+ if LexerATNSimulator.debug:
+ print("DFA after matchATN: " + str(self.decisionToDFA[old_mode].toLexerString()))
+
+ return predict
+
+ def execATN(self, input:InputStream, ds0:DFAState):
+ if LexerATNSimulator.debug:
+ print("start state closure=" + str(ds0.configs))
+
+ if ds0.isAcceptState:
+ # allow zero-length tokens
+ self.captureSimState(self.prevAccept, input, ds0)
+
+ t = input.LA(1)
+ s = ds0 # s is current/from DFA state
+
+ while True: # while more work
+ if LexerATNSimulator.debug:
+ print("execATN loop starting closure:", str(s.configs))
+
+ # As we move src->trg, src->trg, we keep track of the previous trg to
+ # avoid looking up the DFA state again, which is expensive.
+ # If the previous target was already part of the DFA, we might
+ # be able to avoid doing a reach operation upon t. If s!=null,
+ # it means that semantic predicates didn't prevent us from
+ # creating a DFA state. Once we know s!=null, we check to see if
+ # the DFA state has an edge already for t. If so, we can just reuse
+ # it's configuration set; there's no point in re-computing it.
+ # This is kind of like doing DFA simulation within the ATN
+ # simulation because DFA simulation is really just a way to avoid
+ # computing reach/closure sets. Technically, once we know that
+ # we have a previously added DFA state, we could jump over to
+ # the DFA simulator. But, that would mean popping back and forth
+ # a lot and making things more complicated algorithmically.
+ # This optimization makes a lot of sense for loops within DFA.
+ # A character will take us back to an existing DFA state
+ # that already has lots of edges out of it. e.g., .* in comments.
+ # print("Target for:" + str(s) + " and:" + str(t))
+ target = self.getExistingTargetState(s, t)
+ # print("Existing:" + str(target))
+ if target is None:
+ target = self.computeTargetState(input, s, t)
+ # print("Computed:" + str(target))
+
+ if target == self.ERROR:
+ break
+
+ # If this is a consumable input element, make sure to consume before
+ # capturing the accept state so the input index, line, and char
+ # position accurately reflect the state of the interpreter at the
+ # end of the token.
+ if t != Token.EOF:
+ self.consume(input)
+
+ if target.isAcceptState:
+ self.captureSimState(self.prevAccept, input, target)
+ if t == Token.EOF:
+ break
+
+ t = input.LA(1)
+
+ s = target # flip; current DFA target becomes new src/from state
+
+ return self.failOrAccept(self.prevAccept, input, s.configs, t)
+
+ # Get an existing target state for an edge in the DFA. If the target state
+ # for the edge has not yet been computed or is otherwise not available,
+ # this method returns {@code null}.
+ #
+ # @param s The current DFA state
+ # @param t The next input symbol
+ # @return The existing target DFA state for the given input symbol
+ # {@code t}, or {@code null} if the target state for this edge is not
+ # already cached
+ def getExistingTargetState(self, s:DFAState, t:int):
+ if s.edges is None or t < self.MIN_DFA_EDGE or t > self.MAX_DFA_EDGE:
+ return None
+
+ target = s.edges[t - self.MIN_DFA_EDGE]
+ if LexerATNSimulator.debug and target is not None:
+ print("reuse state", str(s.stateNumber), "edge to", str(target.stateNumber))
+
+ return target
+
+ # Compute a target state for an edge in the DFA, and attempt to add the
+ # computed state and corresponding edge to the DFA.
+ #
+ # @param input The input stream
+ # @param s The current DFA state
+ # @param t The next input symbol
+ #
+ # @return The computed target DFA state for the given input symbol
+ # {@code t}. If {@code t} does not lead to a valid DFA state, this method
+ # returns {@link #ERROR}.
+ def computeTargetState(self, input:InputStream, s:DFAState, t:int):
+ reach = OrderedATNConfigSet()
+
+ # if we don't find an existing DFA state
+ # Fill reach starting from closure, following t transitions
+ self.getReachableConfigSet(input, s.configs, reach, t)
+
+ if len(reach)==0: # we got nowhere on t from s
+ if not reach.hasSemanticContext:
+ # we got nowhere on t, don't throw out this knowledge; it'd
+ # cause a failover from DFA later.
+ self. addDFAEdge(s, t, self.ERROR)
+
+ # stop when we can't match any more char
+ return self.ERROR
+
+ # Add an edge from s to target DFA found/created for reach
+ return self.addDFAEdge(s, t, cfgs=reach)
+
+ def failOrAccept(self, prevAccept:SimState , input:InputStream, reach:ATNConfigSet, t:int):
+ if self.prevAccept.dfaState is not None:
+ lexerActionExecutor = prevAccept.dfaState.lexerActionExecutor
+ self.accept(input, lexerActionExecutor, self.startIndex, prevAccept.index, prevAccept.line, prevAccept.column)
+ return prevAccept.dfaState.prediction
+ else:
+ # if no accept and EOF is first char, return EOF
+ if t==Token.EOF and input.index==self.startIndex:
+ return Token.EOF
+ raise LexerNoViableAltException(self.recog, input, self.startIndex, reach)
+
+ # Given a starting configuration set, figure out all ATN configurations
+ # we can reach upon input {@code t}. Parameter {@code reach} is a return
+ # parameter.
+ def getReachableConfigSet(self, input:InputStream, closure:ATNConfigSet, reach:ATNConfigSet, t:int):
+ # this is used to skip processing for configs which have a lower priority
+ # than a config that already reached an accept state for the same rule
+ skipAlt = ATN.INVALID_ALT_NUMBER
+ for cfg in closure:
+ currentAltReachedAcceptState = ( cfg.alt == skipAlt )
+ if currentAltReachedAcceptState and cfg.passedThroughNonGreedyDecision:
+ continue
+
+ if LexerATNSimulator.debug:
+ print("testing", self.getTokenName(t), "at", str(cfg))
+
+ for trans in cfg.state.transitions: # for each transition
+ target = self.getReachableTarget(trans, t)
+ if target is not None:
+ lexerActionExecutor = cfg.lexerActionExecutor
+ if lexerActionExecutor is not None:
+ lexerActionExecutor = lexerActionExecutor.fixOffsetBeforeMatch(input.index - self.startIndex)
+
+ treatEofAsEpsilon = (t == Token.EOF)
+ config = LexerATNConfig(state=target, lexerActionExecutor=lexerActionExecutor, config=cfg)
+ if self.closure(input, config, reach, currentAltReachedAcceptState, True, treatEofAsEpsilon):
+ # any remaining configs for this alt have a lower priority than
+ # the one that just reached an accept state.
+ skipAlt = cfg.alt
+
+ def accept(self, input:InputStream, lexerActionExecutor:LexerActionExecutor, startIndex:int, index:int, line:int, charPos:int):
+ if LexerATNSimulator.debug:
+ print("ACTION", lexerActionExecutor)
+
+ # seek to after last char in token
+ input.seek(index)
+ self.line = line
+ self.column = charPos
+
+ if lexerActionExecutor is not None and self.recog is not None:
+ lexerActionExecutor.execute(self.recog, input, startIndex)
+
+ def getReachableTarget(self, trans:Transition, t:int):
+ if trans.matches(t, 0, self.MAX_CHAR_VALUE):
+ return trans.target
+ else:
+ return None
+
+ def computeStartState(self, input:InputStream, p:ATNState):
+ initialContext = PredictionContext.EMPTY
+ configs = OrderedATNConfigSet()
+ for i in range(0,len(p.transitions)):
+ target = p.transitions[i].target
+ c = LexerATNConfig(state=target, alt=i+1, context=initialContext)
+ self.closure(input, c, configs, False, False, False)
+ return configs
+
+ # Since the alternatives within any lexer decision are ordered by
+ # preference, this method stops pursuing the closure as soon as an accept
+ # state is reached. After the first accept state is reached by depth-first
+ # search from {@code config}, all other (potentially reachable) states for
+ # this rule would have a lower priority.
+ #
+ # @return {@code true} if an accept state is reached, otherwise
+ # {@code false}.
+ def closure(self, input:InputStream, config:LexerATNConfig, configs:ATNConfigSet, currentAltReachedAcceptState:bool,
+ speculative:bool, treatEofAsEpsilon:bool):
+ if LexerATNSimulator.debug:
+ print("closure(" + str(config) + ")")
+
+ if isinstance( config.state, RuleStopState ):
+ if LexerATNSimulator.debug:
+ if self.recog is not None:
+ print("closure at", self.recog.symbolicNames[config.state.ruleIndex], "rule stop", str(config))
+ else:
+ print("closure at rule stop", str(config))
+
+ if config.context is None or config.context.hasEmptyPath():
+ if config.context is None or config.context.isEmpty():
+ configs.add(config)
+ return True
+ else:
+ configs.add(LexerATNConfig(state=config.state, config=config, context=PredictionContext.EMPTY))
+ currentAltReachedAcceptState = True
+
+ if config.context is not None and not config.context.isEmpty():
+ for i in range(0,len(config.context)):
+ if config.context.getReturnState(i) != PredictionContext.EMPTY_RETURN_STATE:
+ newContext = config.context.getParent(i) # "pop" return state
+ returnState = self.atn.states[config.context.getReturnState(i)]
+ c = LexerATNConfig(state=returnState, config=config, context=newContext)
+ currentAltReachedAcceptState = self.closure(input, c, configs,
+ currentAltReachedAcceptState, speculative, treatEofAsEpsilon)
+
+ return currentAltReachedAcceptState
+
+ # optimization
+ if not config.state.epsilonOnlyTransitions:
+ if not currentAltReachedAcceptState or not config.passedThroughNonGreedyDecision:
+ configs.add(config)
+
+ for t in config.state.transitions:
+ c = self.getEpsilonTarget(input, config, t, configs, speculative, treatEofAsEpsilon)
+ if c is not None:
+ currentAltReachedAcceptState = self.closure(input, c, configs, currentAltReachedAcceptState, speculative, treatEofAsEpsilon)
+
+ return currentAltReachedAcceptState
+
+ # side-effect: can alter configs.hasSemanticContext
+ def getEpsilonTarget(self, input:InputStream, config:LexerATNConfig, t:Transition, configs:ATNConfigSet,
+ speculative:bool, treatEofAsEpsilon:bool):
+ c = None
+ if t.serializationType==Transition.RULE:
+ newContext = SingletonPredictionContext.create(config.context, t.followState.stateNumber)
+ c = LexerATNConfig(state=t.target, config=config, context=newContext)
+
+ elif t.serializationType==Transition.PRECEDENCE:
+ raise UnsupportedOperationException("Precedence predicates are not supported in lexers.")
+
+ elif t.serializationType==Transition.PREDICATE:
+ # Track traversing semantic predicates. If we traverse,
+ # we cannot add a DFA state for this "reach" computation
+ # because the DFA would not test the predicate again in the
+ # future. Rather than creating collections of semantic predicates
+ # like v3 and testing them on prediction, v4 will test them on the
+ # fly all the time using the ATN not the DFA. This is slower but
+ # semantically it's not used that often. One of the key elements to
+ # this predicate mechanism is not adding DFA states that see
+ # predicates immediately afterwards in the ATN. For example,
+
+ # a : ID {p1}? | ID {p2}? ;
+
+ # should create the start state for rule 'a' (to save start state
+ # competition), but should not create target of ID state. The
+ # collection of ATN states the following ID references includes
+ # states reached by traversing predicates. Since this is when we
+ # test them, we cannot cash the DFA state target of ID.
+
+ if LexerATNSimulator.debug:
+ print("EVAL rule "+ str(t.ruleIndex) + ":" + str(t.predIndex))
+ configs.hasSemanticContext = True
+ if self.evaluatePredicate(input, t.ruleIndex, t.predIndex, speculative):
+ c = LexerATNConfig(state=t.target, config=config)
+
+ elif t.serializationType==Transition.ACTION:
+ if config.context is None or config.context.hasEmptyPath():
+ # execute actions anywhere in the start rule for a token.
+ #
+ # TODO: if the entry rule is invoked recursively, some
+ # actions may be executed during the recursive call. The
+ # problem can appear when hasEmptyPath() is true but
+ # isEmpty() is false. In this case, the config needs to be
+ # split into two contexts - one with just the empty path
+ # and another with everything but the empty path.
+ # Unfortunately, the current algorithm does not allow
+ # getEpsilonTarget to return two configurations, so
+ # additional modifications are needed before we can support
+ # the split operation.
+ lexerActionExecutor = LexerActionExecutor.append(config.lexerActionExecutor,
+ self.atn.lexerActions[t.actionIndex])
+ c = LexerATNConfig(state=t.target, config=config, lexerActionExecutor=lexerActionExecutor)
+
+ else:
+ # ignore actions in referenced rules
+ c = LexerATNConfig(state=t.target, config=config)
+
+ elif t.serializationType==Transition.EPSILON:
+ c = LexerATNConfig(state=t.target, config=config)
+
+ elif t.serializationType in [ Transition.ATOM, Transition.RANGE, Transition.SET ]:
+ if treatEofAsEpsilon:
+ if t.matches(Token.EOF, 0, self.MAX_CHAR_VALUE):
+ c = LexerATNConfig(state=t.target, config=config)
+
+ return c
+
+ # Evaluate a predicate specified in the lexer.
+ #
+ #
If {@code speculative} is {@code true}, this method was called before
+ # {@link #consume} for the matched character. This method should call
+ # {@link #consume} before evaluating the predicate to ensure position
+ # sensitive values, including {@link Lexer#getText}, {@link Lexer#getLine},
+ # and {@link Lexer#getcolumn}, properly reflect the current
+ # lexer state. This method should restore {@code input} and the simulator
+ # to the original state before returning (i.e. undo the actions made by the
+ # call to {@link #consume}.
+ #
+ # @param input The input stream.
+ # @param ruleIndex The rule containing the predicate.
+ # @param predIndex The index of the predicate within the rule.
+ # @param speculative {@code true} if the current index in {@code input} is
+ # one character before the predicate's location.
+ #
+ # @return {@code true} if the specified predicate evaluates to
+ # {@code true}.
+ #/
+ def evaluatePredicate(self, input:InputStream, ruleIndex:int, predIndex:int, speculative:bool):
+ # assume true if no recognizer was provided
+ if self.recog is None:
+ return True
+
+ if not speculative:
+ return self.recog.sempred(None, ruleIndex, predIndex)
+
+ savedcolumn = self.column
+ savedLine = self.line
+ index = input.index
+ marker = input.mark()
+ try:
+ self.consume(input)
+ return self.recog.sempred(None, ruleIndex, predIndex)
+ finally:
+ self.column = savedcolumn
+ self.line = savedLine
+ input.seek(index)
+ input.release(marker)
+
+ def captureSimState(self, settings:SimState, input:InputStream, dfaState:DFAState):
+ settings.index = input.index
+ settings.line = self.line
+ settings.column = self.column
+ settings.dfaState = dfaState
+
+ def addDFAEdge(self, from_:DFAState, tk:int, to:DFAState=None, cfgs:ATNConfigSet=None) -> DFAState:
+
+ if to is None and cfgs is not None:
+ # leading to this call, ATNConfigSet.hasSemanticContext is used as a
+ # marker indicating dynamic predicate evaluation makes this edge
+ # dependent on the specific input sequence, so the static edge in the
+ # DFA should be omitted. The target DFAState is still created since
+ # execATN has the ability to resynchronize with the DFA state cache
+ # following the predicate evaluation step.
+ #
+ # TJP notes: next time through the DFA, we see a pred again and eval.
+ # If that gets us to a previously created (but dangling) DFA
+ # state, we can continue in pure DFA mode from there.
+ #/
+ suppressEdge = cfgs.hasSemanticContext
+ cfgs.hasSemanticContext = False
+
+ to = self.addDFAState(cfgs)
+
+ if suppressEdge:
+ return to
+
+ # add the edge
+ if tk < self.MIN_DFA_EDGE or tk > self.MAX_DFA_EDGE:
+ # Only track edges within the DFA bounds
+ return to
+
+ if LexerATNSimulator.debug:
+ print("EDGE " + str(from_) + " -> " + str(to) + " upon "+ chr(tk))
+
+ if from_.edges is None:
+ # make room for tokens 1..n and -1 masquerading as index 0
+ from_.edges = [ None ] * (self.MAX_DFA_EDGE - self.MIN_DFA_EDGE + 1)
+
+ from_.edges[tk - self.MIN_DFA_EDGE] = to # connect
+
+ return to
+
+
+ # Add a new DFA state if there isn't one with this set of
+ # configurations already. This method also detects the first
+ # configuration containing an ATN rule stop state. Later, when
+ # traversing the DFA, we will know which rule to accept.
+ def addDFAState(self, configs:ATNConfigSet) -> DFAState:
+
+ proposed = DFAState(configs=configs)
+ firstConfigWithRuleStopState = next((cfg for cfg in configs if isinstance(cfg.state, RuleStopState)), None)
+
+ if firstConfigWithRuleStopState is not None:
+ proposed.isAcceptState = True
+ proposed.lexerActionExecutor = firstConfigWithRuleStopState.lexerActionExecutor
+ proposed.prediction = self.atn.ruleToTokenType[firstConfigWithRuleStopState.state.ruleIndex]
+
+ dfa = self.decisionToDFA[self.mode]
+ existing = dfa.states.get(proposed, None)
+ if existing is not None:
+ return existing
+
+ newState = proposed
+
+ newState.stateNumber = len(dfa.states)
+ configs.setReadonly(True)
+ newState.configs = configs
+ dfa.states[newState] = newState
+ return newState
+
+ def getDFA(self, mode:int):
+ return self.decisionToDFA[mode]
+
+ # Get the text matched so far for the current token.
+ def getText(self, input:InputStream):
+ # index is first lookahead char, don't include.
+ return input.getText(self.startIndex, input.index-1)
+
+ def consume(self, input:InputStream):
+ curChar = input.LA(1)
+ if curChar==ord('\n'):
+ self.line += 1
+ self.column = 0
+ else:
+ self.column += 1
+ input.consume()
+
+ def getTokenName(self, t:int):
+ if t==-1:
+ return "EOF"
+ else:
+ return "'" + chr(t) + "'"
+
+
+LexerATNSimulator.ERROR = DFAState(0x7FFFFFFF, ATNConfigSet())
+
+del Lexer
diff --git a/cameras/deps/antlr4/atn/LexerAction.py b/cameras/deps/antlr4/atn/LexerAction.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fa7a895f31cea4415b71def30ca1ec90167f1b3
--- /dev/null
+++ b/cameras/deps/antlr4/atn/LexerAction.py
@@ -0,0 +1,298 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+ #
+
+from enum import IntEnum
+
+# need forward declaration
+Lexer = None
+
+
+class LexerActionType(IntEnum):
+
+ CHANNEL = 0 #The type of a {@link LexerChannelAction} action.
+ CUSTOM = 1 #The type of a {@link LexerCustomAction} action.
+ MODE = 2 #The type of a {@link LexerModeAction} action.
+ MORE = 3 #The type of a {@link LexerMoreAction} action.
+ POP_MODE = 4 #The type of a {@link LexerPopModeAction} action.
+ PUSH_MODE = 5 #The type of a {@link LexerPushModeAction} action.
+ SKIP = 6 #The type of a {@link LexerSkipAction} action.
+ TYPE = 7 #The type of a {@link LexerTypeAction} action.
+
+class LexerAction(object):
+ __slots__ = ('actionType', 'isPositionDependent')
+
+ def __init__(self, action:LexerActionType):
+ self.actionType = action
+ self.isPositionDependent = False
+
+ def __hash__(self):
+ return hash(self.actionType)
+
+ def __eq__(self, other):
+ return self is other
+
+
+#
+# Implements the {@code skip} lexer action by calling {@link Lexer#skip}.
+#
+#
The {@code skip} command does not have any parameters, so this action is
+# implemented as a singleton instance exposed by {@link #INSTANCE}.
This action is implemented by calling {@link Lexer#mode} with the
+ # value provided by {@link #getMode}.
+ def execute(self, lexer:Lexer):
+ lexer.mode(self.mode)
+
+ def __hash__(self):
+ return hash((self.actionType, self.mode))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerModeAction):
+ return False
+ else:
+ return self.mode == other.mode
+
+ def __str__(self):
+ return "mode(" + str(self.mode) + ")"
+
+# Executes a custom lexer action by calling {@link Recognizer#action} with the
+# rule and action indexes assigned to the custom action. The implementation of
+# a custom action is added to the generated code for the lexer in an override
+# of {@link Recognizer#action} when the grammar is compiled.
+#
+#
This class may represent embedded actions created with the {...}
+# syntax in ANTLR 4, as well as actions created for lexer commands where the
+# command argument could not be evaluated when the grammar was compiled.
+
+class LexerCustomAction(LexerAction):
+ __slots__ = ('ruleIndex', 'actionIndex')
+
+ # Constructs a custom lexer action with the specified rule and action
+ # indexes.
+ #
+ # @param ruleIndex The rule index to use for calls to
+ # {@link Recognizer#action}.
+ # @param actionIndex The action index to use for calls to
+ # {@link Recognizer#action}.
+ #/
+ def __init__(self, ruleIndex:int, actionIndex:int):
+ super().__init__(LexerActionType.CUSTOM)
+ self.ruleIndex = ruleIndex
+ self.actionIndex = actionIndex
+ self.isPositionDependent = True
+
+ #
Custom actions are implemented by calling {@link Lexer#action} with the
+ # appropriate rule and action indexes.
+ def execute(self, lexer:Lexer):
+ lexer.action(None, self.ruleIndex, self.actionIndex)
+
+ def __hash__(self):
+ return hash((self.actionType, self.ruleIndex, self.actionIndex))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerCustomAction):
+ return False
+ else:
+ return self.ruleIndex == other.ruleIndex and self.actionIndex == other.actionIndex
+
+# Implements the {@code channel} lexer action by calling
+# {@link Lexer#setChannel} with the assigned channel.
+class LexerChannelAction(LexerAction):
+ __slots__ = 'channel'
+
+ # Constructs a new {@code channel} action with the specified channel value.
+ # @param channel The channel value to pass to {@link Lexer#setChannel}.
+ def __init__(self, channel:int):
+ super().__init__(LexerActionType.CHANNEL)
+ self.channel = channel
+
+ #
This action is implemented by calling {@link Lexer#setChannel} with the
+ # value provided by {@link #getChannel}.
+ def execute(self, lexer:Lexer):
+ lexer._channel = self.channel
+
+ def __hash__(self):
+ return hash((self.actionType, self.channel))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerChannelAction):
+ return False
+ else:
+ return self.channel == other.channel
+
+ def __str__(self):
+ return "channel(" + str(self.channel) + ")"
+
+# This implementation of {@link LexerAction} is used for tracking input offsets
+# for position-dependent actions within a {@link LexerActionExecutor}.
+#
+#
This action is not serialized as part of the ATN, and is only required for
+# position-dependent lexer actions which appear at a location other than the
+# end of a rule. For more information about DFA optimizations employed for
+# lexer actions, see {@link LexerActionExecutor#append} and
+# {@link LexerActionExecutor#fixOffsetBeforeMatch}.
+class LexerIndexedCustomAction(LexerAction):
+ __slots__ = ('offset', 'action')
+
+ # Constructs a new indexed custom action by associating a character offset
+ # with a {@link LexerAction}.
+ #
+ #
Note: This class is only required for lexer actions for which
+ # {@link LexerAction#isPositionDependent} returns {@code true}.
+ #
+ # @param offset The offset into the input {@link CharStream}, relative to
+ # the token start index, at which the specified lexer action should be
+ # executed.
+ # @param action The lexer action to execute at a particular offset in the
+ # input {@link CharStream}.
+ def __init__(self, offset:int, action:LexerAction):
+ super().__init__(action.actionType)
+ self.offset = offset
+ self.action = action
+ self.isPositionDependent = True
+
+ #
This method calls {@link #execute} on the result of {@link #getAction}
+ # using the provided {@code lexer}.
+ def execute(self, lexer:Lexer):
+ # assume the input stream position was properly set by the calling code
+ self.action.execute(lexer)
+
+ def __hash__(self):
+ return hash((self.actionType, self.offset, self.action))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerIndexedCustomAction):
+ return False
+ else:
+ return self.offset == other.offset and self.action == other.action
diff --git a/cameras/deps/antlr4/atn/LexerActionExecutor.py b/cameras/deps/antlr4/atn/LexerActionExecutor.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c6462c3a28f4ccbcb0a65dcde96f497dd392416
--- /dev/null
+++ b/cameras/deps/antlr4/atn/LexerActionExecutor.py
@@ -0,0 +1,143 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+# Represents an executor for a sequence of lexer actions which traversed during
+# the matching operation of a lexer rule (token).
+#
+#
The executor tracks position information for position-dependent lexer actions
+# efficiently, ensuring that actions appearing only at the end of the rule do
+# not cause bloating of the {@link DFA} created for the lexer.
+
+
+from antlr4.InputStream import InputStream
+from antlr4.atn.LexerAction import LexerAction, LexerIndexedCustomAction
+
+# need a forward declaration
+Lexer = None
+LexerActionExecutor = None
+
+class LexerActionExecutor(object):
+ __slots__ = ('lexerActions', 'hashCode')
+
+ def __init__(self, lexerActions:list=list()):
+ self.lexerActions = lexerActions
+ # Caches the result of {@link #hashCode} since the hash code is an element
+ # of the performance-critical {@link LexerATNConfig#hashCode} operation.
+ self.hashCode = hash("".join([str(la) for la in lexerActions]))
+
+
+ # Creates a {@link LexerActionExecutor} which executes the actions for
+ # the input {@code lexerActionExecutor} followed by a specified
+ # {@code lexerAction}.
+ #
+ # @param lexerActionExecutor The executor for actions already traversed by
+ # the lexer while matching a token within a particular
+ # {@link LexerATNConfig}. If this is {@code null}, the method behaves as
+ # though it were an empty executor.
+ # @param lexerAction The lexer action to execute after the actions
+ # specified in {@code lexerActionExecutor}.
+ #
+ # @return A {@link LexerActionExecutor} for executing the combine actions
+ # of {@code lexerActionExecutor} and {@code lexerAction}.
+ @staticmethod
+ def append(lexerActionExecutor:LexerActionExecutor , lexerAction:LexerAction ):
+ if lexerActionExecutor is None:
+ return LexerActionExecutor([ lexerAction ])
+
+ lexerActions = lexerActionExecutor.lexerActions + [ lexerAction ]
+ return LexerActionExecutor(lexerActions)
+
+ # Creates a {@link LexerActionExecutor} which encodes the current offset
+ # for position-dependent lexer actions.
+ #
+ #
Normally, when the executor encounters lexer actions where
+ # {@link LexerAction#isPositionDependent} returns {@code true}, it calls
+ # {@link IntStream#seek} on the input {@link CharStream} to set the input
+ # position to the end of the current token. This behavior provides
+ # for efficient DFA representation of lexer actions which appear at the end
+ # of a lexer rule, even when the lexer rule matches a variable number of
+ # characters.
+ #
+ #
Prior to traversing a match transition in the ATN, the current offset
+ # from the token start index is assigned to all position-dependent lexer
+ # actions which have not already been assigned a fixed offset. By storing
+ # the offsets relative to the token start index, the DFA representation of
+ # lexer actions which appear in the middle of tokens remains efficient due
+ # to sharing among tokens of the same length, regardless of their absolute
+ # position in the input stream.
+ #
+ #
If the current executor already has offsets assigned to all
+ # position-dependent lexer actions, the method returns {@code this}.
+ #
+ # @param offset The current offset to assign to all position-dependent
+ # lexer actions which do not already have offsets assigned.
+ #
+ # @return A {@link LexerActionExecutor} which stores input stream offsets
+ # for all position-dependent lexer actions.
+ #/
+ def fixOffsetBeforeMatch(self, offset:int):
+ updatedLexerActions = None
+ for i in range(0, len(self.lexerActions)):
+ if self.lexerActions[i].isPositionDependent and not isinstance(self.lexerActions[i], LexerIndexedCustomAction):
+ if updatedLexerActions is None:
+ updatedLexerActions = [ la for la in self.lexerActions ]
+ updatedLexerActions[i] = LexerIndexedCustomAction(offset, self.lexerActions[i])
+
+ if updatedLexerActions is None:
+ return self
+ else:
+ return LexerActionExecutor(updatedLexerActions)
+
+
+ # Execute the actions encapsulated by this executor within the context of a
+ # particular {@link Lexer}.
+ #
+ #
This method calls {@link IntStream#seek} to set the position of the
+ # {@code input} {@link CharStream} prior to calling
+ # {@link LexerAction#execute} on a position-dependent action. Before the
+ # method returns, the input position will be restored to the same position
+ # it was in when the method was invoked.
+ #
+ # @param lexer The lexer instance.
+ # @param input The input stream which is the source for the current token.
+ # When this method is called, the current {@link IntStream#index} for
+ # {@code input} should be the start of the following token, i.e. 1
+ # character past the end of the current token.
+ # @param startIndex The token start index. This value may be passed to
+ # {@link IntStream#seek} to set the {@code input} position to the beginning
+ # of the token.
+ #/
+ def execute(self, lexer:Lexer, input:InputStream, startIndex:int):
+ requiresSeek = False
+ stopIndex = input.index
+ try:
+ for lexerAction in self.lexerActions:
+ if isinstance(lexerAction, LexerIndexedCustomAction):
+ offset = lexerAction.offset
+ input.seek(startIndex + offset)
+ lexerAction = lexerAction.action
+ requiresSeek = (startIndex + offset) != stopIndex
+ elif lexerAction.isPositionDependent:
+ input.seek(stopIndex)
+ requiresSeek = False
+ lexerAction.execute(lexer)
+ finally:
+ if requiresSeek:
+ input.seek(stopIndex)
+
+ def __hash__(self):
+ return self.hashCode
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerActionExecutor):
+ return False
+ else:
+ return self.hashCode == other.hashCode \
+ and self.lexerActions == other.lexerActions
+
+del Lexer
diff --git a/cameras/deps/antlr4/atn/ParserATNSimulator.py b/cameras/deps/antlr4/atn/ParserATNSimulator.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1fb3d7ed8bab47fb851d85da88d20dc39715627
--- /dev/null
+++ b/cameras/deps/antlr4/atn/ParserATNSimulator.py
@@ -0,0 +1,1649 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# The embodiment of the adaptive LL(*), ALL(*), parsing strategy.
+#
+#
+# The basic complexity of the adaptive strategy makes it harder to understand.
+# We begin with ATN simulation to build paths in a DFA. Subsequent prediction
+# requests go through the DFA first. If they reach a state without an edge for
+# the current symbol, the algorithm fails over to the ATN simulation to
+# complete the DFA path for the current input (until it finds a conflict state
+# or uniquely predicting state).
+#
+#
+# All of that is done without using the outer context because we want to create
+# a DFA that is not dependent upon the rule invocation stack when we do a
+# prediction. One DFA works in all contexts. We avoid using context not
+# necessarily because it's slower, although it can be, but because of the DFA
+# caching problem. The closure routine only considers the rule invocation stack
+# created during prediction beginning in the decision rule. For example, if
+# prediction occurs without invoking another rule's ATN, there are no context
+# stacks in the configurations. When lack of context leads to a conflict, we
+# don't know if it's an ambiguity or a weakness in the strong LL(*) parsing
+# strategy (versus full LL(*)).
+#
+#
+# When SLL yields a configuration set with conflict, we rewind the input and
+# retry the ATN simulation, this time using full outer context without adding
+# to the DFA. Configuration context stacks will be the full invocation stacks
+# from the start rule. If we get a conflict using full context, then we can
+# definitively say we have a true ambiguity for that input sequence. If we
+# don't get a conflict, it implies that the decision is sensitive to the outer
+# context. (It is not context-sensitive in the sense of context-sensitive
+# grammars.)
+#
+#
+# The next time we reach this DFA state with an SLL conflict, through DFA
+# simulation, we will again retry the ATN simulation using full context mode.
+# This is slow because we can't save the results and have to "interpret" the
+# ATN each time we get that input.
+#
+#
+# CACHING FULL CONTEXT PREDICTIONS
+#
+#
+# We could cache results from full context to predicted alternative easily and
+# that saves a lot of time but doesn't work in presence of predicates. The set
+# of visible predicates from the ATN start state changes depending on the
+# context, because closure can fall off the end of a rule. I tried to cache
+# tuples (stack context, semantic context, predicted alt) but it was slower
+# than interpreting and much more complicated. Also required a huge amount of
+# memory. The goal is not to create the world's fastest parser anyway. I'd like
+# to keep this algorithm simple. By launching multiple threads, we can improve
+# the speed of parsing across a large number of files.
+#
+#
+# There is no strict ordering between the amount of input used by SLL vs LL,
+# which makes it really hard to build a cache for full context. Let's say that
+# we have input A B C that leads to an SLL conflict with full context X. That
+# implies that using X we might only use A B but we could also use A B C D to
+# resolve conflict. Input A B C D could predict alternative 1 in one position
+# in the input and A B C E could predict alternative 2 in another position in
+# input. The conflicting SLL configurations could still be non-unique in the
+# full context prediction, which would lead us to requiring more input than the
+# original A B C. To make a prediction cache work, we have to track the exact
+# input used during the previous prediction. That amounts to a cache that maps
+# X to a specific DFA for that context.
+#
+#
+# Something should be done for left-recursive expression predictions. They are
+# likely LL(1) + pred eval. Easier to do the whole SLL unless error and retry
+# with full LL thing Sam does.
+#
+#
+# AVOIDING FULL CONTEXT PREDICTION
+#
+#
+# We avoid doing full context retry when the outer context is empty, we did not
+# dip into the outer context by falling off the end of the decision state rule,
+# or when we force SLL mode.
+#
+#
+# As an example of the not dip into outer context case, consider as super
+# constructor calls versus function calls. One grammar might look like
+# this:
+# In both cases I believe that no closure operations will dip into the outer
+# context. In the first case ctorBody in the worst case will stop at the '}'.
+# In the 2nd case it should stop at the ';'. Both cases should stay within the
+# entry rule and not dip into the outer context.
+#
+#
+# PREDICATES
+#
+#
+# Predicates are always evaluated if present in either SLL or LL both. SLL and
+# LL simulation deals with predicates differently. SLL collects predicates as
+# it performs closure operations like ANTLR v3 did. It delays predicate
+# evaluation until it reaches and accept state. This allows us to cache the SLL
+# ATN simulation whereas, if we had evaluated predicates on-the-fly during
+# closure, the DFA state configuration sets would be different and we couldn't
+# build up a suitable DFA.
+#
+#
+# When building a DFA accept state during ATN simulation, we evaluate any
+# predicates and return the sole semantically valid alternative. If there is
+# more than 1 alternative, we report an ambiguity. If there are 0 alternatives,
+# we throw an exception. Alternatives without predicates act like they have
+# true predicates. The simple way to think about it is to strip away all
+# alternatives with false predicates and choose the minimum alternative that
+# remains.
+#
+#
+# When we start in the DFA and reach an accept state that's predicated, we test
+# those and return the minimum semantically viable alternative. If no
+# alternatives are viable, we throw an exception.
+#
+#
+# During full LL ATN simulation, closure always evaluates predicates and
+# on-the-fly. This is crucial to reducing the configuration set size during
+# closure. It hits a landmine when parsing with the Java grammar, for example,
+# without this on-the-fly evaluation.
+#
+#
+# SHARING DFA
+#
+#
+# All instances of the same parser share the same decision DFAs through a
+# static field. Each instance gets its own ATN simulator but they share the
+# same {@link #decisionToDFA} field. They also share a
+# {@link PredictionContextCache} object that makes sure that all
+# {@link PredictionContext} objects are shared among the DFA states. This makes
+# a big size difference.
+#
+#
+# THREAD SAFETY
+#
+#
+# The {@link ParserATNSimulator} locks on the {@link #decisionToDFA} field when
+# it adds a new DFA object to that array. {@link #addDFAEdge}
+# locks on the DFA for the current decision when setting the
+# {@link DFAState#edges} field. {@link #addDFAState} locks on
+# the DFA for the current decision when looking up a DFA state to see if it
+# already exists. We must make sure that all requests to add DFA states that
+# are equivalent result in the same shared DFA object. This is because lots of
+# threads will be trying to update the DFA at once. The
+# {@link #addDFAState} method also locks inside the DFA lock
+# but this time on the shared context cache when it rebuilds the
+# configurations' {@link PredictionContext} objects using cached
+# subgraphs/nodes. No other locking occurs, even during DFA simulation. This is
+# safe as long as we can guarantee that all threads referencing
+# {@code s.edge[t]} get the same physical target {@link DFAState}, or
+# {@code null}. Once into the DFA, the DFA simulation does not reference the
+# {@link DFA#states} map. It follows the {@link DFAState#edges} field to new
+# targets. The DFA simulator will either find {@link DFAState#edges} to be
+# {@code null}, to be non-{@code null} and {@code dfa.edges[t]} null, or
+# {@code dfa.edges[t]} to be non-null. The
+# {@link #addDFAEdge} method could be racing to set the field
+# but in either case the DFA simulator works; if {@code null}, and requests ATN
+# simulation. It could also race trying to get {@code dfa.edges[t]}, but either
+# way it will work because it's not doing a test and set operation.
+#
+#
+# Starting with SLL then failing to combined SLL/LL (Two-Stage
+# Parsing)
+#
+#
+# Sam pointed out that if SLL does not give a syntax error, then there is no
+# point in doing full LL, which is slower. We only have to try LL if we get a
+# syntax error. For maximum speed, Sam starts the parser set to pure SLL
+# mode with the {@link BailErrorStrategy}:
+# If it does not get a syntax error, then we're done. If it does get a syntax
+# error, we need to retry with the combined SLL/LL strategy.
+#
+#
+# The reason this works is as follows. If there are no SLL conflicts, then the
+# grammar is SLL (at least for that input set). If there is an SLL conflict,
+# the full LL analysis must yield a set of viable alternatives which is a
+# subset of the alternatives reported by SLL. If the LL set is a singleton,
+# then the grammar is LL but not SLL. If the LL set is the same size as the SLL
+# set, the decision is SLL. If the LL set has size > 1, then that decision
+# is truly ambiguous on the current input. If the LL set is smaller, then the
+# SLL conflict resolution might choose an alternative that the full LL would
+# rule out as a possibility based upon better context information. If that's
+# the case, then the SLL parse will definitely get an error because the full LL
+# analysis says it's not viable. If SLL conflict resolution chooses an
+# alternative within the LL set, them both SLL and LL would choose the same
+# alternative because they both choose the minimum of multiple conflicting
+# alternatives.
+#
+#
+# Let's say we have a set of SLL conflicting alternatives {@code {1, 2, 3}} and
+# a smaller LL set called s. If s is {@code {2, 3}}, then SLL
+# parsing will get an error because SLL will pursue alternative 1. If
+# s is {@code {1, 2}} or {@code {1, 3}} then both SLL and LL will
+# choose the same alternative because alternative one is the minimum of either
+# set. If s is {@code {2}} or {@code {3}} then SLL will get a syntax
+# error. If s is {@code {1}} then SLL will succeed.
+#
+#
+# Of course, if the input is invalid, then we will get an error for sure in
+# both SLL and LL parsing. Erroneous input will therefore require 2 passes over
+# the input.
+#
+import sys
+from antlr4 import DFA
+from antlr4.PredictionContext import PredictionContextCache, PredictionContext, SingletonPredictionContext, \
+ PredictionContextFromRuleContext
+from antlr4.BufferedTokenStream import TokenStream
+from antlr4.Parser import Parser
+from antlr4.ParserRuleContext import ParserRuleContext
+from antlr4.RuleContext import RuleContext
+from antlr4.Token import Token
+from antlr4.Utils import str_list
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNConfig import ATNConfig
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.atn.ATNSimulator import ATNSimulator
+from antlr4.atn.ATNState import StarLoopEntryState, DecisionState, RuleStopState, ATNState
+from antlr4.atn.PredictionMode import PredictionMode
+from antlr4.atn.SemanticContext import SemanticContext, AND, andContext, orContext
+from antlr4.atn.Transition import Transition, RuleTransition, ActionTransition, PrecedencePredicateTransition, \
+ PredicateTransition, AtomTransition, SetTransition, NotSetTransition
+from antlr4.dfa.DFAState import DFAState, PredPrediction
+from antlr4.error.Errors import NoViableAltException
+
+
+class ParserATNSimulator(ATNSimulator):
+ __slots__ = (
+ 'parser', 'decisionToDFA', 'predictionMode', '_input', '_startIndex',
+ '_outerContext', '_dfa', 'mergeCache'
+ )
+
+ debug = False
+ debug_list_atn_decisions = False
+ dfa_debug = False
+ retry_debug = False
+
+
+ def __init__(self, parser:Parser, atn:ATN, decisionToDFA:list, sharedContextCache:PredictionContextCache):
+ super().__init__(atn, sharedContextCache)
+ self.parser = parser
+ self.decisionToDFA = decisionToDFA
+ # SLL, LL, or LL + exact ambig detection?#
+ self.predictionMode = PredictionMode.LL
+ # LAME globals to avoid parameters!!!!! I need these down deep in predTransition
+ self._input = None
+ self._startIndex = 0
+ self._outerContext = None
+ self._dfa = None
+ # Each prediction operation uses a cache for merge of prediction contexts.
+ # Don't keep around as it wastes huge amounts of memory. DoubleKeyMap
+ # isn't synchronized but we're ok since two threads shouldn't reuse same
+ # parser/atnsim object because it can only handle one input at a time.
+ # This maps graphs a and b to merged result c. (a,b)→c. We can avoid
+ # the merge if we ever see a and b again. Note that (b,a)→c should
+ # also be examined during cache lookup.
+ #
+ self.mergeCache = None
+
+
+ def reset(self):
+ pass
+
+ def adaptivePredict(self, input:TokenStream, decision:int, outerContext:ParserRuleContext):
+ if ParserATNSimulator.debug or ParserATNSimulator.debug_list_atn_decisions:
+ print("adaptivePredict decision " + str(decision) +
+ " exec LA(1)==" + self.getLookaheadName(input) +
+ " line " + str(input.LT(1).line) + ":" +
+ str(input.LT(1).column))
+ self._input = input
+ self._startIndex = input.index
+ self._outerContext = outerContext
+
+ dfa = self.decisionToDFA[decision]
+ self._dfa = dfa
+ m = input.mark()
+ index = input.index
+
+ # Now we are certain to have a specific decision's DFA
+ # But, do we still need an initial state?
+ try:
+ if dfa.precedenceDfa:
+ # the start state for a precedence DFA depends on the current
+ # parser precedence, and is provided by a DFA method.
+ s0 = dfa.getPrecedenceStartState(self.parser.getPrecedence())
+ else:
+ # the start state for a "regular" DFA is just s0
+ s0 = dfa.s0
+
+ if s0 is None:
+ if outerContext is None:
+ outerContext = ParserRuleContext.EMPTY
+ if ParserATNSimulator.debug or ParserATNSimulator.debug_list_atn_decisions:
+ print("predictATN decision " + str(dfa.decision) +
+ " exec LA(1)==" + self.getLookaheadName(input) +
+ ", outerContext=" + outerContext.toString(self.parser.literalNames, None))
+
+ fullCtx = False
+ s0_closure = self.computeStartState(dfa.atnStartState, ParserRuleContext.EMPTY, fullCtx)
+
+ if dfa.precedenceDfa:
+ # If this is a precedence DFA, we use applyPrecedenceFilter
+ # to convert the computed start state to a precedence start
+ # state. We then use DFA.setPrecedenceStartState to set the
+ # appropriate start state for the precedence level rather
+ # than simply setting DFA.s0.
+ #
+ dfa.s0.configs = s0_closure # not used for prediction but useful to know start configs anyway
+ s0_closure = self.applyPrecedenceFilter(s0_closure)
+ s0 = self.addDFAState(dfa, DFAState(configs=s0_closure))
+ dfa.setPrecedenceStartState(self.parser.getPrecedence(), s0)
+ else:
+ s0 = self.addDFAState(dfa, DFAState(configs=s0_closure))
+ dfa.s0 = s0
+
+ alt = self.execATN(dfa, s0, input, index, outerContext)
+ if ParserATNSimulator.debug:
+ print("DFA after predictATN: " + dfa.toString(self.parser.literalNames))
+ return alt
+ finally:
+ self._dfa = None
+ self.mergeCache = None # wack cache after each prediction
+ input.seek(index)
+ input.release(m)
+
+ # Performs ATN simulation to compute a predicted alternative based
+ # upon the remaining input, but also updates the DFA cache to avoid
+ # having to traverse the ATN again for the same input sequence.
+
+ # There are some key conditions we're looking for after computing a new
+ # set of ATN configs (proposed DFA state):
+ # if the set is empty, there is no viable alternative for current symbol
+ # does the state uniquely predict an alternative?
+ # does the state have a conflict that would prevent us from
+ # putting it on the work list?
+
+ # We also have some key operations to do:
+ # add an edge from previous DFA state to potentially new DFA state, D,
+ # upon current symbol but only if adding to work list, which means in all
+ # cases except no viable alternative (and possibly non-greedy decisions?)
+ # collecting predicates and adding semantic context to DFA accept states
+ # adding rule context to context-sensitive DFA accept states
+ # consuming an input symbol
+ # reporting a conflict
+ # reporting an ambiguity
+ # reporting a context sensitivity
+ # reporting insufficient predicates
+
+ # cover these cases:
+ # dead end
+ # single alt
+ # single alt + preds
+ # conflict
+ # conflict + preds
+ #
+ def execATN(self, dfa:DFA, s0:DFAState, input:TokenStream, startIndex:int, outerContext:ParserRuleContext ):
+ if ParserATNSimulator.debug or ParserATNSimulator.debug_list_atn_decisions:
+ print("execATN decision " + str(dfa.decision) +
+ " exec LA(1)==" + self.getLookaheadName(input) +
+ " line " + str(input.LT(1).line) + ":" + str(input.LT(1).column))
+
+ previousD = s0
+
+ if ParserATNSimulator.debug:
+ print("s0 = " + str(s0))
+
+ t = input.LA(1)
+
+ while True: # while more work
+ D = self.getExistingTargetState(previousD, t)
+ if D is None:
+ D = self.computeTargetState(dfa, previousD, t)
+ if D is self.ERROR:
+ # if any configs in previous dipped into outer context, that
+ # means that input up to t actually finished entry rule
+ # at least for SLL decision. Full LL doesn't dip into outer
+ # so don't need special case.
+ # We will get an error no matter what so delay until after
+ # decision; better error message. Also, no reachable target
+ # ATN states in SLL implies LL will also get nowhere.
+ # If conflict in states that dip out, choose min since we
+ # will get error no matter what.
+ e = self.noViableAlt(input, outerContext, previousD.configs, startIndex)
+ input.seek(startIndex)
+ alt = self.getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(previousD.configs, outerContext)
+ if alt!=ATN.INVALID_ALT_NUMBER:
+ return alt
+ raise e
+
+ if D.requiresFullContext and self.predictionMode != PredictionMode.SLL:
+ # IF PREDS, MIGHT RESOLVE TO SINGLE ALT => SLL (or syntax error)
+ conflictingAlts = D.configs.conflictingAlts
+ if D.predicates is not None:
+ if ParserATNSimulator.debug:
+ print("DFA state has preds in DFA sim LL failover")
+ conflictIndex = input.index
+ if conflictIndex != startIndex:
+ input.seek(startIndex)
+
+ conflictingAlts = self.evalSemanticContext(D.predicates, outerContext, True)
+ if len(conflictingAlts)==1:
+ if ParserATNSimulator.debug:
+ print("Full LL avoided")
+ return min(conflictingAlts)
+
+ if conflictIndex != startIndex:
+ # restore the index so reporting the fallback to full
+ # context occurs with the index at the correct spot
+ input.seek(conflictIndex)
+
+ if ParserATNSimulator.dfa_debug:
+ print("ctx sensitive state " + str(outerContext) +" in " + str(D))
+ fullCtx = True
+ s0_closure = self.computeStartState(dfa.atnStartState, outerContext, fullCtx)
+ self.reportAttemptingFullContext(dfa, conflictingAlts, D.configs, startIndex, input.index)
+ alt = self.execATNWithFullContext(dfa, D, s0_closure, input, startIndex, outerContext)
+ return alt
+
+ if D.isAcceptState:
+ if D.predicates is None:
+ return D.prediction
+
+ stopIndex = input.index
+ input.seek(startIndex)
+ alts = self.evalSemanticContext(D.predicates, outerContext, True)
+ if len(alts)==0:
+ raise self.noViableAlt(input, outerContext, D.configs, startIndex)
+ elif len(alts)==1:
+ return min(alts)
+ else:
+ # report ambiguity after predicate evaluation to make sure the correct
+ # set of ambig alts is reported.
+ self.reportAmbiguity(dfa, D, startIndex, stopIndex, False, alts, D.configs)
+ return min(alts)
+
+ previousD = D
+
+ if t != Token.EOF:
+ input.consume()
+ t = input.LA(1)
+
+ #
+ # Get an existing target state for an edge in the DFA. If the target state
+ # for the edge has not yet been computed or is otherwise not available,
+ # this method returns {@code null}.
+ #
+ # @param previousD The current DFA state
+ # @param t The next input symbol
+ # @return The existing target DFA state for the given input symbol
+ # {@code t}, or {@code null} if the target state for this edge is not
+ # already cached
+ #
+ def getExistingTargetState(self, previousD:DFAState, t:int):
+ edges = previousD.edges
+ if edges is None or t + 1 < 0 or t + 1 >= len(edges):
+ return None
+ else:
+ return edges[t + 1]
+
+ #
+ # Compute a target state for an edge in the DFA, and attempt to add the
+ # computed state and corresponding edge to the DFA.
+ #
+ # @param dfa The DFA
+ # @param previousD The current DFA state
+ # @param t The next input symbol
+ #
+ # @return The computed target DFA state for the given input symbol
+ # {@code t}. If {@code t} does not lead to a valid DFA state, this method
+ # returns {@link #ERROR}.
+ #
+ def computeTargetState(self, dfa:DFA, previousD:DFAState, t:int):
+ reach = self.computeReachSet(previousD.configs, t, False)
+ if reach is None:
+ self.addDFAEdge(dfa, previousD, t, self.ERROR)
+ return self.ERROR
+
+ # create new target state; we'll add to DFA after it's complete
+ D = DFAState(configs=reach)
+
+ predictedAlt = self.getUniqueAlt(reach)
+
+ if ParserATNSimulator.debug:
+ altSubSets = PredictionMode.getConflictingAltSubsets(reach)
+ print("SLL altSubSets=" + str(altSubSets) + ", configs=" + str(reach) +
+ ", predict=" + str(predictedAlt) + ", allSubsetsConflict=" +
+ str(PredictionMode.allSubsetsConflict(altSubSets)) + ", conflictingAlts=" +
+ str(self.getConflictingAlts(reach)))
+
+ if predictedAlt!=ATN.INVALID_ALT_NUMBER:
+ # NO CONFLICT, UNIQUELY PREDICTED ALT
+ D.isAcceptState = True
+ D.configs.uniqueAlt = predictedAlt
+ D.prediction = predictedAlt
+ elif PredictionMode.hasSLLConflictTerminatingPrediction(self.predictionMode, reach):
+ # MORE THAN ONE VIABLE ALTERNATIVE
+ D.configs.conflictingAlts = self.getConflictingAlts(reach)
+ D.requiresFullContext = True
+ # in SLL-only mode, we will stop at this state and return the minimum alt
+ D.isAcceptState = True
+ D.prediction = min(D.configs.conflictingAlts)
+
+ if D.isAcceptState and D.configs.hasSemanticContext:
+ self.predicateDFAState(D, self.atn.getDecisionState(dfa.decision))
+ if D.predicates is not None:
+ D.prediction = ATN.INVALID_ALT_NUMBER
+
+ # all adds to dfa are done after we've created full D state
+ D = self.addDFAEdge(dfa, previousD, t, D)
+ return D
+
+ def predicateDFAState(self, dfaState:DFAState, decisionState:DecisionState):
+ # We need to test all predicates, even in DFA states that
+ # uniquely predict alternative.
+ nalts = len(decisionState.transitions)
+ # Update DFA so reach becomes accept state with (predicate,alt)
+ # pairs if preds found for conflicting alts
+ altsToCollectPredsFrom = self.getConflictingAltsOrUniqueAlt(dfaState.configs)
+ altToPred = self.getPredsForAmbigAlts(altsToCollectPredsFrom, dfaState.configs, nalts)
+ if altToPred is not None:
+ dfaState.predicates = self.getPredicatePredictions(altsToCollectPredsFrom, altToPred)
+ dfaState.prediction = ATN.INVALID_ALT_NUMBER # make sure we use preds
+ else:
+ # There are preds in configs but they might go away
+ # when OR'd together like {p}? || NONE == NONE. If neither
+ # alt has preds, resolve to min alt
+ dfaState.prediction = min(altsToCollectPredsFrom)
+
+ # comes back with reach.uniqueAlt set to a valid alt
+ def execATNWithFullContext(self, dfa:DFA, D:DFAState, # how far we got before failing over
+ s0:ATNConfigSet,
+ input:TokenStream,
+ startIndex:int,
+ outerContext:ParserRuleContext):
+ if ParserATNSimulator.debug or ParserATNSimulator.debug_list_atn_decisions:
+ print("execATNWithFullContext", str(s0))
+ fullCtx = True
+ foundExactAmbig = False
+ reach = None
+ previous = s0
+ input.seek(startIndex)
+ t = input.LA(1)
+ predictedAlt = -1
+ while (True): # while more work
+ reach = self.computeReachSet(previous, t, fullCtx)
+ if reach is None:
+ # if any configs in previous dipped into outer context, that
+ # means that input up to t actually finished entry rule
+ # at least for LL decision. Full LL doesn't dip into outer
+ # so don't need special case.
+ # We will get an error no matter what so delay until after
+ # decision; better error message. Also, no reachable target
+ # ATN states in SLL implies LL will also get nowhere.
+ # If conflict in states that dip out, choose min since we
+ # will get error no matter what.
+ e = self.noViableAlt(input, outerContext, previous, startIndex)
+ input.seek(startIndex)
+ alt = self.getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(previous, outerContext)
+ if alt!=ATN.INVALID_ALT_NUMBER:
+ return alt
+ else:
+ raise e
+
+ altSubSets = PredictionMode.getConflictingAltSubsets(reach)
+ if ParserATNSimulator.debug:
+ print("LL altSubSets=" + str(altSubSets) + ", predict=" +
+ str(PredictionMode.getUniqueAlt(altSubSets)) + ", resolvesToJustOneViableAlt=" +
+ str(PredictionMode.resolvesToJustOneViableAlt(altSubSets)))
+
+ reach.uniqueAlt = self.getUniqueAlt(reach)
+ # unique prediction?
+ if reach.uniqueAlt!=ATN.INVALID_ALT_NUMBER:
+ predictedAlt = reach.uniqueAlt
+ break
+ elif self.predictionMode is not PredictionMode.LL_EXACT_AMBIG_DETECTION:
+ predictedAlt = PredictionMode.resolvesToJustOneViableAlt(altSubSets)
+ if predictedAlt != ATN.INVALID_ALT_NUMBER:
+ break
+ else:
+ # In exact ambiguity mode, we never try to terminate early.
+ # Just keeps scarfing until we know what the conflict is
+ if PredictionMode.allSubsetsConflict(altSubSets) and PredictionMode.allSubsetsEqual(altSubSets):
+ foundExactAmbig = True
+ predictedAlt = PredictionMode.getSingleViableAlt(altSubSets)
+ break
+ # else there are multiple non-conflicting subsets or
+ # we're not sure what the ambiguity is yet.
+ # So, keep going.
+
+ previous = reach
+ if t != Token.EOF:
+ input.consume()
+ t = input.LA(1)
+
+ # If the configuration set uniquely predicts an alternative,
+ # without conflict, then we know that it's a full LL decision
+ # not SLL.
+ if reach.uniqueAlt != ATN.INVALID_ALT_NUMBER :
+ self.reportContextSensitivity(dfa, predictedAlt, reach, startIndex, input.index)
+ return predictedAlt
+
+ # We do not check predicates here because we have checked them
+ # on-the-fly when doing full context prediction.
+
+ #
+ # In non-exact ambiguity detection mode, we might actually be able to
+ # detect an exact ambiguity, but I'm not going to spend the cycles
+ # needed to check. We only emit ambiguity warnings in exact ambiguity
+ # mode.
+ #
+ # For example, we might know that we have conflicting configurations.
+ # But, that does not mean that there is no way forward without a
+ # conflict. It's possible to have nonconflicting alt subsets as in:
+
+ # altSubSets=[{1, 2}, {1, 2}, {1}, {1, 2}]
+
+ # from
+ #
+ # [(17,1,[5 $]), (13,1,[5 10 $]), (21,1,[5 10 $]), (11,1,[$]),
+ # (13,2,[5 10 $]), (21,2,[5 10 $]), (11,2,[$])]
+ #
+ # In this case, (17,1,[5 $]) indicates there is some next sequence that
+ # would resolve this without conflict to alternative 1. Any other viable
+ # next sequence, however, is associated with a conflict. We stop
+ # looking for input because no amount of further lookahead will alter
+ # the fact that we should predict alternative 1. We just can't say for
+ # sure that there is an ambiguity without looking further.
+
+ self.reportAmbiguity(dfa, D, startIndex, input.index, foundExactAmbig, None, reach)
+
+ return predictedAlt
+
+ def computeReachSet(self, closure:ATNConfigSet, t:int, fullCtx:bool):
+ if ParserATNSimulator.debug:
+ print("in computeReachSet, starting closure: " + str(closure))
+
+ if self.mergeCache is None:
+ self.mergeCache = dict()
+
+ intermediate = ATNConfigSet(fullCtx)
+
+ # Configurations already in a rule stop state indicate reaching the end
+ # of the decision rule (local context) or end of the start rule (full
+ # context). Once reached, these configurations are never updated by a
+ # closure operation, so they are handled separately for the performance
+ # advantage of having a smaller intermediate set when calling closure.
+ #
+ # For full-context reach operations, separate handling is required to
+ # ensure that the alternative matching the longest overall sequence is
+ # chosen when multiple such configurations can match the input.
+
+ skippedStopStates = None
+
+ # First figure out where we can reach on input t
+ for c in closure:
+ if ParserATNSimulator.debug:
+ print("testing " + self.getTokenName(t) + " at " + str(c))
+
+ if isinstance(c.state, RuleStopState):
+ if fullCtx or t == Token.EOF:
+ if skippedStopStates is None:
+ skippedStopStates = list()
+ skippedStopStates.append(c)
+ continue
+
+ for trans in c.state.transitions:
+ target = self.getReachableTarget(trans, t)
+ if target is not None:
+ intermediate.add(ATNConfig(state=target, config=c), self.mergeCache)
+
+ # Now figure out where the reach operation can take us...
+
+ reach = None
+
+ # This block optimizes the reach operation for intermediate sets which
+ # trivially indicate a termination state for the overall
+ # adaptivePredict operation.
+ #
+ # The conditions assume that intermediate
+ # contains all configurations relevant to the reach set, but this
+ # condition is not true when one or more configurations have been
+ # withheld in skippedStopStates, or when the current symbol is EOF.
+ #
+ if skippedStopStates is None and t!=Token.EOF:
+ if len(intermediate)==1:
+ # Don't pursue the closure if there is just one state.
+ # It can only have one alternative; just add to result
+ # Also don't pursue the closure if there is unique alternative
+ # among the configurations.
+ reach = intermediate
+ elif self.getUniqueAlt(intermediate)!=ATN.INVALID_ALT_NUMBER:
+ # Also don't pursue the closure if there is unique alternative
+ # among the configurations.
+ reach = intermediate
+
+ # If the reach set could not be trivially determined, perform a closure
+ # operation on the intermediate set to compute its initial value.
+ #
+ if reach is None:
+ reach = ATNConfigSet(fullCtx)
+ closureBusy = set()
+ treatEofAsEpsilon = t == Token.EOF
+ for c in intermediate:
+ self.closure(c, reach, closureBusy, False, fullCtx, treatEofAsEpsilon)
+
+ if t == Token.EOF:
+ # After consuming EOF no additional input is possible, so we are
+ # only interested in configurations which reached the end of the
+ # decision rule (local context) or end of the start rule (full
+ # context). Update reach to contain only these configurations. This
+ # handles both explicit EOF transitions in the grammar and implicit
+ # EOF transitions following the end of the decision or start rule.
+ #
+ # When reach==intermediate, no closure operation was performed. In
+ # this case, removeAllConfigsNotInRuleStopState needs to check for
+ # reachable rule stop states as well as configurations already in
+ # a rule stop state.
+ #
+ # This is handled before the configurations in skippedStopStates,
+ # because any configurations potentially added from that list are
+ # already guaranteed to meet this condition whether or not it's
+ # required.
+ #
+ reach = self.removeAllConfigsNotInRuleStopState(reach, reach is intermediate)
+
+ # If skippedStopStates is not null, then it contains at least one
+ # configuration. For full-context reach operations, these
+ # configurations reached the end of the start rule, in which case we
+ # only add them back to reach if no configuration during the current
+ # closure operation reached such a state. This ensures adaptivePredict
+ # chooses an alternative matching the longest overall sequence when
+ # multiple alternatives are viable.
+ #
+ if skippedStopStates is not None and ( (not fullCtx) or (not PredictionMode.hasConfigInRuleStopState(reach))):
+ for c in skippedStopStates:
+ reach.add(c, self.mergeCache)
+ if len(reach)==0:
+ return None
+ else:
+ return reach
+
+ #
+ # Return a configuration set containing only the configurations from
+ # {@code configs} which are in a {@link RuleStopState}. If all
+ # configurations in {@code configs} are already in a rule stop state, this
+ # method simply returns {@code configs}.
+ #
+ #
When {@code lookToEndOfRule} is true, this method uses
+ # {@link ATN#nextTokens} for each configuration in {@code configs} which is
+ # not already in a rule stop state to see if a rule stop state is reachable
+ # from the configuration via epsilon-only transitions.
+ #
+ # @param configs the configuration set to update
+ # @param lookToEndOfRule when true, this method checks for rule stop states
+ # reachable by epsilon-only transitions from each configuration in
+ # {@code configs}.
+ #
+ # @return {@code configs} if all configurations in {@code configs} are in a
+ # rule stop state, otherwise return a new configuration set containing only
+ # the configurations from {@code configs} which are in a rule stop state
+ #
+ def removeAllConfigsNotInRuleStopState(self, configs:ATNConfigSet, lookToEndOfRule:bool):
+ if PredictionMode.allConfigsInRuleStopStates(configs):
+ return configs
+ result = ATNConfigSet(configs.fullCtx)
+ for config in configs:
+ if isinstance(config.state, RuleStopState):
+ result.add(config, self.mergeCache)
+ continue
+ if lookToEndOfRule and config.state.epsilonOnlyTransitions:
+ nextTokens = self.atn.nextTokens(config.state)
+ if Token.EPSILON in nextTokens:
+ endOfRuleState = self.atn.ruleToStopState[config.state.ruleIndex]
+ result.add(ATNConfig(state=endOfRuleState, config=config), self.mergeCache)
+ return result
+
+ def computeStartState(self, p:ATNState, ctx:RuleContext, fullCtx:bool):
+ # always at least the implicit call to start rule
+ initialContext = PredictionContextFromRuleContext(self.atn, ctx)
+ configs = ATNConfigSet(fullCtx)
+
+ for i in range(0, len(p.transitions)):
+ target = p.transitions[i].target
+ c = ATNConfig(target, i+1, initialContext)
+ closureBusy = set()
+ self.closure(c, configs, closureBusy, True, fullCtx, False)
+ return configs
+
+ #
+ # This method transforms the start state computed by
+ # {@link #computeStartState} to the special start state used by a
+ # precedence DFA for a particular precedence value. The transformation
+ # process applies the following changes to the start state's configuration
+ # set.
+ #
+ #
+ #
Evaluate the precedence predicates for each configuration using
+ # {@link SemanticContext#evalPrecedence}.
+ #
Remove all configurations which predict an alternative greater than
+ # 1, for which another configuration that predicts alternative 1 is in the
+ # same ATN state with the same prediction context. This transformation is
+ # valid for the following reasons:
+ #
+ #
The closure block cannot contain any epsilon transitions which bypass
+ # the body of the closure, so all states reachable via alternative 1 are
+ # part of the precedence alternatives of the transformed left-recursive
+ # rule.
+ #
The "primary" portion of a left recursive rule cannot contain an
+ # epsilon transition, so the only way an alternative other than 1 can exist
+ # in a state that is also reachable via alternative 1 is by nesting calls
+ # to the left-recursive rule, with the outer calls not being at the
+ # preferred precedence level.
+ #
+ #
+ #
+ #
+ #
+ # The prediction context must be considered by this filter to address
+ # situations like the following.
+ #
+ # If the above grammar, the ATN state immediately before the token
+ # reference {@code 'a'} in {@code letterA} is reachable from the left edge
+ # of both the primary and closure blocks of the left-recursive rule
+ # {@code statement}. The prediction context associated with each of these
+ # configurations distinguishes between them, and prevents the alternative
+ # which stepped out to {@code prog} (and then back in to {@code statement}
+ # from being eliminated by the filter.
+ #
+ #
+ # @param configs The configuration set computed by
+ # {@link #computeStartState} as the start state for the DFA.
+ # @return The transformed configuration set representing the start state
+ # for a precedence DFA at a particular precedence level (determined by
+ # calling {@link Parser#getPrecedence}).
+ #
+ def applyPrecedenceFilter(self, configs:ATNConfigSet):
+ statesFromAlt1 = dict()
+ configSet = ATNConfigSet(configs.fullCtx)
+ for config in configs:
+ # handle alt 1 first
+ if config.alt != 1:
+ continue
+ updatedContext = config.semanticContext.evalPrecedence(self.parser, self._outerContext)
+ if updatedContext is None:
+ # the configuration was eliminated
+ continue
+
+ statesFromAlt1[config.state.stateNumber] = config.context
+ if updatedContext is not config.semanticContext:
+ configSet.add(ATNConfig(config=config, semantic=updatedContext), self.mergeCache)
+ else:
+ configSet.add(config, self.mergeCache)
+
+ for config in configs:
+ if config.alt == 1:
+ # already handled
+ continue
+
+ # In the future, this elimination step could be updated to also
+ # filter the prediction context for alternatives predicting alt>1
+ # (basically a graph subtraction algorithm).
+ #
+ if not config.precedenceFilterSuppressed:
+ context = statesFromAlt1.get(config.state.stateNumber, None)
+ if context==config.context:
+ # eliminated
+ continue
+
+ configSet.add(config, self.mergeCache)
+
+ return configSet
+
+ def getReachableTarget(self, trans:Transition, ttype:int):
+ if trans.matches(ttype, 0, self.atn.maxTokenType):
+ return trans.target
+ else:
+ return None
+
+ def getPredsForAmbigAlts(self, ambigAlts:set, configs:ATNConfigSet, nalts:int):
+ # REACH=[1|1|[]|0:0, 1|2|[]|0:1]
+ # altToPred starts as an array of all null contexts. The entry at index i
+ # corresponds to alternative i. altToPred[i] may have one of three values:
+ # 1. null: no ATNConfig c is found such that c.alt==i
+ # 2. SemanticContext.NONE: At least one ATNConfig c exists such that
+ # c.alt==i and c.semanticContext==SemanticContext.NONE. In other words,
+ # alt i has at least one unpredicated config.
+ # 3. Non-NONE Semantic Context: There exists at least one, and for all
+ # ATNConfig c such that c.alt==i, c.semanticContext!=SemanticContext.NONE.
+ #
+ # From this, it is clear that NONE||anything==NONE.
+ #
+ altToPred = [None] * (nalts + 1)
+ for c in configs:
+ if c.alt in ambigAlts:
+ altToPred[c.alt] = orContext(altToPred[c.alt], c.semanticContext)
+
+ nPredAlts = 0
+ for i in range(1, nalts+1):
+ if altToPred[i] is None:
+ altToPred[i] = SemanticContext.NONE
+ elif altToPred[i] is not SemanticContext.NONE:
+ nPredAlts += 1
+
+ # nonambig alts are null in altToPred
+ if nPredAlts==0:
+ altToPred = None
+ if ParserATNSimulator.debug:
+ print("getPredsForAmbigAlts result " + str_list(altToPred))
+ return altToPred
+
+ def getPredicatePredictions(self, ambigAlts:set, altToPred:list):
+ pairs = []
+ containsPredicate = False
+ for i in range(1, len(altToPred)):
+ pred = altToPred[i]
+ # unpredicated is indicated by SemanticContext.NONE
+ if ambigAlts is not None and i in ambigAlts:
+ pairs.append(PredPrediction(pred, i))
+ if pred is not SemanticContext.NONE:
+ containsPredicate = True
+
+ if not containsPredicate:
+ return None
+
+ return pairs
+
+ #
+ # This method is used to improve the localization of error messages by
+ # choosing an alternative rather than throwing a
+ # {@link NoViableAltException} in particular prediction scenarios where the
+ # {@link #ERROR} state was reached during ATN simulation.
+ #
+ #
+ # The default implementation of this method uses the following
+ # algorithm to identify an ATN configuration which successfully parsed the
+ # decision entry rule. Choosing such an alternative ensures that the
+ # {@link ParserRuleContext} returned by the calling rule will be complete
+ # and valid, and the syntax error will be reported later at a more
+ # localized location.
+ #
+ #
+ #
If a syntactically valid path or paths reach the end of the decision rule and
+ # they are semantically valid if predicated, return the min associated alt.
+ #
Else, if a semantically invalid but syntactically valid path exist
+ # or paths exist, return the minimum associated alt.
+ #
+ #
Otherwise, return {@link ATN#INVALID_ALT_NUMBER}.
+ #
+ #
+ #
+ # In some scenarios, the algorithm described above could predict an
+ # alternative which will result in a {@link FailedPredicateException} in
+ # the parser. Specifically, this could occur if the only configuration
+ # capable of successfully parsing to the end of the decision rule is
+ # blocked by a semantic predicate. By choosing this alternative within
+ # {@link #adaptivePredict} instead of throwing a
+ # {@link NoViableAltException}, the resulting
+ # {@link FailedPredicateException} in the parser will identify the specific
+ # predicate which is preventing the parser from successfully parsing the
+ # decision rule, which helps developers identify and correct logic errors
+ # in semantic predicates.
+ #
+ #
+ # @param configs The ATN configurations which were valid immediately before
+ # the {@link #ERROR} state was reached
+ # @param outerContext The is the \gamma_0 initial parser context from the paper
+ # or the parser stack at the instant before prediction commences.
+ #
+ # @return The value to return from {@link #adaptivePredict}, or
+ # {@link ATN#INVALID_ALT_NUMBER} if a suitable alternative was not
+ # identified and {@link #adaptivePredict} should report an error instead.
+ #
+ def getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(self, configs:ATNConfigSet, outerContext:ParserRuleContext):
+ semValidConfigs, semInvalidConfigs = self.splitAccordingToSemanticValidity(configs, outerContext)
+ alt = self.getAltThatFinishedDecisionEntryRule(semValidConfigs)
+ if alt!=ATN.INVALID_ALT_NUMBER: # semantically/syntactically viable path exists
+ return alt
+ # Is there a syntactically valid path with a failed pred?
+ if len(semInvalidConfigs)>0:
+ alt = self.getAltThatFinishedDecisionEntryRule(semInvalidConfigs)
+ if alt!=ATN.INVALID_ALT_NUMBER: # syntactically viable path exists
+ return alt
+ return ATN.INVALID_ALT_NUMBER
+
+ def getAltThatFinishedDecisionEntryRule(self, configs:ATNConfigSet):
+ alts = set()
+ for c in configs:
+ if c.reachesIntoOuterContext>0 or (isinstance(c.state, RuleStopState) and c.context.hasEmptyPath() ):
+ alts.add(c.alt)
+ if len(alts)==0:
+ return ATN.INVALID_ALT_NUMBER
+ else:
+ return min(alts)
+
+ # Walk the list of configurations and split them according to
+ # those that have preds evaluating to true/false. If no pred, assume
+ # true pred and include in succeeded set. Returns Pair of sets.
+ #
+ # Create a new set so as not to alter the incoming parameter.
+ #
+ # Assumption: the input stream has been restored to the starting point
+ # prediction, which is where predicates need to evaluate.
+ #
+ def splitAccordingToSemanticValidity(self, configs:ATNConfigSet, outerContext:ParserRuleContext):
+ succeeded = ATNConfigSet(configs.fullCtx)
+ failed = ATNConfigSet(configs.fullCtx)
+ for c in configs:
+ if c.semanticContext is not SemanticContext.NONE:
+ predicateEvaluationResult = c.semanticContext.eval(self.parser, outerContext)
+ if predicateEvaluationResult:
+ succeeded.add(c)
+ else:
+ failed.add(c)
+ else:
+ succeeded.add(c)
+ return (succeeded,failed)
+
+ # Look through a list of predicate/alt pairs, returning alts for the
+ # pairs that win. A {@code NONE} predicate indicates an alt containing an
+ # unpredicated config which behaves as "always true." If !complete
+ # then we stop at the first predicate that evaluates to true. This
+ # includes pairs with null predicates.
+ #
+ def evalSemanticContext(self, predPredictions:list, outerContext:ParserRuleContext, complete:bool):
+ predictions = set()
+ for pair in predPredictions:
+ if pair.pred is SemanticContext.NONE:
+ predictions.add(pair.alt)
+ if not complete:
+ break
+ continue
+ predicateEvaluationResult = pair.pred.eval(self.parser, outerContext)
+ if ParserATNSimulator.debug or ParserATNSimulator.dfa_debug:
+ print("eval pred " + str(pair) + "=" + str(predicateEvaluationResult))
+
+ if predicateEvaluationResult:
+ if ParserATNSimulator.debug or ParserATNSimulator.dfa_debug:
+ print("PREDICT " + str(pair.alt))
+ predictions.add(pair.alt)
+ if not complete:
+ break
+ return predictions
+
+
+ # TODO: If we are doing predicates, there is no point in pursuing
+ # closure operations if we reach a DFA state that uniquely predicts
+ # alternative. We will not be caching that DFA state and it is a
+ # waste to pursue the closure. Might have to advance when we do
+ # ambig detection thought :(
+ #
+
+ def closure(self, config:ATNConfig, configs:ATNConfigSet, closureBusy:set, collectPredicates:bool, fullCtx:bool, treatEofAsEpsilon:bool):
+ initialDepth = 0
+ self.closureCheckingStopState(config, configs, closureBusy, collectPredicates,
+ fullCtx, initialDepth, treatEofAsEpsilon)
+
+
+ def closureCheckingStopState(self, config:ATNConfig, configs:ATNConfigSet, closureBusy:set, collectPredicates:bool, fullCtx:bool, depth:int, treatEofAsEpsilon:bool):
+ if ParserATNSimulator.debug:
+ print("closure(" + str(config) + ")")
+
+ if isinstance(config.state, RuleStopState):
+ # We hit rule end. If we have context info, use it
+ # run thru all possible stack tops in ctx
+ if not config.context.isEmpty():
+ for i in range(0, len(config.context)):
+ state = config.context.getReturnState(i)
+ if state is PredictionContext.EMPTY_RETURN_STATE:
+ if fullCtx:
+ configs.add(ATNConfig(state=config.state, context=PredictionContext.EMPTY, config=config), self.mergeCache)
+ continue
+ else:
+ # we have no context info, just chase follow links (if greedy)
+ if ParserATNSimulator.debug:
+ print("FALLING off rule " + self.getRuleName(config.state.ruleIndex))
+ self.closure_(config, configs, closureBusy, collectPredicates,
+ fullCtx, depth, treatEofAsEpsilon)
+ continue
+ returnState = self.atn.states[state]
+ newContext = config.context.getParent(i) # "pop" return state
+ c = ATNConfig(state=returnState, alt=config.alt, context=newContext, semantic=config.semanticContext)
+ # While we have context to pop back from, we may have
+ # gotten that context AFTER having falling off a rule.
+ # Make sure we track that we are now out of context.
+ c.reachesIntoOuterContext = config.reachesIntoOuterContext
+ self.closureCheckingStopState(c, configs, closureBusy, collectPredicates, fullCtx, depth - 1, treatEofAsEpsilon)
+ return
+ elif fullCtx:
+ # reached end of start rule
+ configs.add(config, self.mergeCache)
+ return
+ else:
+ # else if we have no context info, just chase follow links (if greedy)
+ if ParserATNSimulator.debug:
+ print("FALLING off rule " + self.getRuleName(config.state.ruleIndex))
+
+ self.closure_(config, configs, closureBusy, collectPredicates, fullCtx, depth, treatEofAsEpsilon)
+
+ # Do the actual work of walking epsilon edges#
+ def closure_(self, config:ATNConfig, configs:ATNConfigSet, closureBusy:set, collectPredicates:bool, fullCtx:bool, depth:int, treatEofAsEpsilon:bool):
+ p = config.state
+ # optimization
+ if not p.epsilonOnlyTransitions:
+ configs.add(config, self.mergeCache)
+ # make sure to not return here, because EOF transitions can act as
+ # both epsilon transitions and non-epsilon transitions.
+
+ first = True
+ for t in p.transitions:
+ if first:
+ first = False
+ if self.canDropLoopEntryEdgeInLeftRecursiveRule(config):
+ continue
+
+ continueCollecting = collectPredicates and not isinstance(t, ActionTransition)
+ c = self.getEpsilonTarget(config, t, continueCollecting, depth == 0, fullCtx, treatEofAsEpsilon)
+ if c is not None:
+ newDepth = depth
+ if isinstance( config.state, RuleStopState):
+ # target fell off end of rule; mark resulting c as having dipped into outer context
+ # We can't get here if incoming config was rule stop and we had context
+ # track how far we dip into outer context. Might
+ # come in handy and we avoid evaluating context dependent
+ # preds if this is > 0.
+ if self._dfa is not None and self._dfa.precedenceDfa:
+ if t.outermostPrecedenceReturn == self._dfa.atnStartState.ruleIndex:
+ c.precedenceFilterSuppressed = True
+ c.reachesIntoOuterContext += 1
+ if c in closureBusy:
+ # avoid infinite recursion for right-recursive rules
+ continue
+ closureBusy.add(c)
+ configs.dipsIntoOuterContext = True # TODO: can remove? only care when we add to set per middle of this method
+ newDepth -= 1
+ if ParserATNSimulator.debug:
+ print("dips into outer ctx: " + str(c))
+ else:
+ if not t.isEpsilon:
+ if c in closureBusy:
+ # avoid infinite recursion for EOF* and EOF+
+ continue
+ closureBusy.add(c)
+ if isinstance(t, RuleTransition):
+ # latch when newDepth goes negative - once we step out of the entry context we can't return
+ if newDepth >= 0:
+ newDepth += 1
+
+ self.closureCheckingStopState(c, configs, closureBusy, continueCollecting, fullCtx, newDepth, treatEofAsEpsilon)
+
+
+
+ # Implements first-edge (loop entry) elimination as an optimization
+ # during closure operations. See antlr/antlr4#1398.
+ #
+ # The optimization is to avoid adding the loop entry config when
+ # the exit path can only lead back to the same
+ # StarLoopEntryState after popping context at the rule end state
+ # (traversing only epsilon edges, so we're still in closure, in
+ # this same rule).
+ #
+ # We need to detect any state that can reach loop entry on
+ # epsilon w/o exiting rule. We don't have to look at FOLLOW
+ # links, just ensure that all stack tops for config refer to key
+ # states in LR rule.
+ #
+ # To verify we are in the right situation we must first check
+ # closure is at a StarLoopEntryState generated during LR removal.
+ # Then we check that each stack top of context is a return state
+ # from one of these cases:
+ #
+ # 1. 'not' expr, '(' type ')' expr. The return state points at loop entry state
+ # 2. expr op expr. The return state is the block end of internal block of (...)*
+ # 3. 'between' expr 'and' expr. The return state of 2nd expr reference.
+ # That state points at block end of internal block of (...)*.
+ # 4. expr '?' expr ':' expr. The return state points at block end,
+ # which points at loop entry state.
+ #
+ # If any is true for each stack top, then closure does not add a
+ # config to the current config set for edge[0], the loop entry branch.
+ #
+ # Conditions fail if any context for the current config is:
+ #
+ # a. empty (we'd fall out of expr to do a global FOLLOW which could
+ # even be to some weird spot in expr) or,
+ # b. lies outside of expr or,
+ # c. lies within expr but at a state not the BlockEndState
+ # generated during LR removal
+ #
+ # Do we need to evaluate predicates ever in closure for this case?
+ #
+ # No. Predicates, including precedence predicates, are only
+ # evaluated when computing a DFA start state. I.e., only before
+ # the lookahead (but not parser) consumes a token.
+ #
+ # There are no epsilon edges allowed in LR rule alt blocks or in
+ # the "primary" part (ID here). If closure is in
+ # StarLoopEntryState any lookahead operation will have consumed a
+ # token as there are no epsilon-paths that lead to
+ # StarLoopEntryState. We do not have to evaluate predicates
+ # therefore if we are in the generated StarLoopEntryState of a LR
+ # rule. Note that when making a prediction starting at that
+ # decision point, decision d=2, compute-start-state performs
+ # closure starting at edges[0], edges[1] emanating from
+ # StarLoopEntryState. That means it is not performing closure on
+ # StarLoopEntryState during compute-start-state.
+ #
+ # How do we know this always gives same prediction answer?
+ #
+ # Without predicates, loop entry and exit paths are ambiguous
+ # upon remaining input +b (in, say, a+b). Either paths lead to
+ # valid parses. Closure can lead to consuming + immediately or by
+ # falling out of this call to expr back into expr and loop back
+ # again to StarLoopEntryState to match +b. In this special case,
+ # we choose the more efficient path, which is to take the bypass
+ # path.
+ #
+ # The lookahead language has not changed because closure chooses
+ # one path over the other. Both paths lead to consuming the same
+ # remaining input during a lookahead operation. If the next token
+ # is an operator, lookahead will enter the choice block with
+ # operators. If it is not, lookahead will exit expr. Same as if
+ # closure had chosen to enter the choice block immediately.
+ #
+ # Closure is examining one config (some loopentrystate, some alt,
+ # context) which means it is considering exactly one alt. Closure
+ # always copies the same alt to any derived configs.
+ #
+ # How do we know this optimization doesn't mess up precedence in
+ # our parse trees?
+ #
+ # Looking through expr from left edge of stat only has to confirm
+ # that an input, say, a+b+c; begins with any valid interpretation
+ # of an expression. The precedence actually doesn't matter when
+ # making a decision in stat seeing through expr. It is only when
+ # parsing rule expr that we must use the precedence to get the
+ # right interpretation and, hence, parse tree.
+ #
+ # @since 4.6
+ #
+ def canDropLoopEntryEdgeInLeftRecursiveRule(self, config):
+ # return False
+ p = config.state
+ # First check to see if we are in StarLoopEntryState generated during
+ # left-recursion elimination. For efficiency, also check if
+ # the context has an empty stack case. If so, it would mean
+ # global FOLLOW so we can't perform optimization
+ # Are we the special loop entry/exit state? or SLL wildcard
+ if p.stateType != ATNState.STAR_LOOP_ENTRY \
+ or not p.isPrecedenceDecision \
+ or config.context.isEmpty() \
+ or config.context.hasEmptyPath():
+ return False
+
+ # Require all return states to return back to the same rule
+ # that p is in.
+ numCtxs = len(config.context)
+ for i in range(0, numCtxs): # for each stack context
+ returnState = self.atn.states[config.context.getReturnState(i)]
+ if returnState.ruleIndex != p.ruleIndex:
+ return False
+
+ decisionStartState = p.transitions[0].target
+ blockEndStateNum = decisionStartState.endState.stateNumber
+ blockEndState = self.atn.states[blockEndStateNum]
+
+ # Verify that the top of each stack context leads to loop entry/exit
+ # state through epsilon edges and w/o leaving rule.
+ for i in range(0, numCtxs): # for each stack context
+ returnStateNumber = config.context.getReturnState(i)
+ returnState = self.atn.states[returnStateNumber]
+ # all states must have single outgoing epsilon edge
+ if len(returnState.transitions) != 1 or not returnState.transitions[0].isEpsilon:
+ return False
+
+ # Look for prefix op case like 'not expr', (' type ')' expr
+ returnStateTarget = returnState.transitions[0].target
+ if returnState.stateType == ATNState.BLOCK_END and returnStateTarget is p:
+ continue
+
+ # Look for 'expr op expr' or case where expr's return state is block end
+ # of (...)* internal block; the block end points to loop back
+ # which points to p but we don't need to check that
+ if returnState is blockEndState:
+ continue
+
+ # Look for ternary expr ? expr : expr. The return state points at block end,
+ # which points at loop entry state
+ if returnStateTarget is blockEndState:
+ continue
+
+ # Look for complex prefix 'between expr and expr' case where 2nd expr's
+ # return state points at block end state of (...)* internal block
+ if returnStateTarget.stateType == ATNState.BLOCK_END \
+ and len(returnStateTarget.transitions) == 1 \
+ and returnStateTarget.transitions[0].isEpsilon \
+ and returnStateTarget.transitions[0].target is p:
+ continue
+
+ # anything else ain't conforming
+ return False
+
+ return True
+
+
+ def getRuleName(self, index:int):
+ if self.parser is not None and index>=0:
+ return self.parser.ruleNames[index]
+ else:
+ return ""
+
+ epsilonTargetMethods = dict()
+ epsilonTargetMethods[Transition.RULE] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ sim.ruleTransition(config, t)
+ epsilonTargetMethods[Transition.PRECEDENCE] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ sim.precedenceTransition(config, t, collectPredicates, inContext, fullCtx)
+ epsilonTargetMethods[Transition.PREDICATE] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ sim.predTransition(config, t, collectPredicates, inContext, fullCtx)
+ epsilonTargetMethods[Transition.ACTION] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ sim.actionTransition(config, t)
+ epsilonTargetMethods[Transition.EPSILON] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ ATNConfig(state=t.target, config=config)
+ epsilonTargetMethods[Transition.ATOM] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ ATNConfig(state=t.target, config=config) if treatEofAsEpsilon and t.matches(Token.EOF, 0, 1) else None
+ epsilonTargetMethods[Transition.RANGE] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ ATNConfig(state=t.target, config=config) if treatEofAsEpsilon and t.matches(Token.EOF, 0, 1) else None
+ epsilonTargetMethods[Transition.SET] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ ATNConfig(state=t.target, config=config) if treatEofAsEpsilon and t.matches(Token.EOF, 0, 1) else None
+
+ def getEpsilonTarget(self, config:ATNConfig, t:Transition, collectPredicates:bool, inContext:bool, fullCtx:bool, treatEofAsEpsilon:bool):
+ m = self.epsilonTargetMethods.get(t.serializationType, None)
+ if m is None:
+ return None
+ else:
+ return m(self, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon)
+
+ def actionTransition(self, config:ATNConfig, t:ActionTransition):
+ if ParserATNSimulator.debug:
+ print("ACTION edge " + str(t.ruleIndex) + ":" + str(t.actionIndex))
+ return ATNConfig(state=t.target, config=config)
+
+ def precedenceTransition(self, config:ATNConfig, pt:PrecedencePredicateTransition, collectPredicates:bool, inContext:bool, fullCtx:bool):
+ if ParserATNSimulator.debug:
+ print("PRED (collectPredicates=" + str(collectPredicates) + ") " +
+ str(pt.precedence) + ">=_p, ctx dependent=true")
+ if self.parser is not None:
+ print("context surrounding pred is " + str(self.parser.getRuleInvocationStack()))
+
+ c = None
+ if collectPredicates and inContext:
+ if fullCtx:
+ # In full context mode, we can evaluate predicates on-the-fly
+ # during closure, which dramatically reduces the size of
+ # the config sets. It also obviates the need to test predicates
+ # later during conflict resolution.
+ currentPosition = self._input.index
+ self._input.seek(self._startIndex)
+ predSucceeds = pt.getPredicate().eval(self.parser, self._outerContext)
+ self._input.seek(currentPosition)
+ if predSucceeds:
+ c = ATNConfig(state=pt.target, config=config) # no pred context
+ else:
+ newSemCtx = andContext(config.semanticContext, pt.getPredicate())
+ c = ATNConfig(state=pt.target, semantic=newSemCtx, config=config)
+ else:
+ c = ATNConfig(state=pt.target, config=config)
+
+ if ParserATNSimulator.debug:
+ print("config from pred transition=" + str(c))
+ return c
+
+ def predTransition(self, config:ATNConfig, pt:PredicateTransition, collectPredicates:bool, inContext:bool, fullCtx:bool):
+ if ParserATNSimulator.debug:
+ print("PRED (collectPredicates=" + str(collectPredicates) + ") " + str(pt.ruleIndex) +
+ ":" + str(pt.predIndex) + ", ctx dependent=" + str(pt.isCtxDependent))
+ if self.parser is not None:
+ print("context surrounding pred is " + str(self.parser.getRuleInvocationStack()))
+
+ c = None
+ if collectPredicates and (not pt.isCtxDependent or (pt.isCtxDependent and inContext)):
+ if fullCtx:
+ # In full context mode, we can evaluate predicates on-the-fly
+ # during closure, which dramatically reduces the size of
+ # the config sets. It also obviates the need to test predicates
+ # later during conflict resolution.
+ currentPosition = self._input.index
+ self._input.seek(self._startIndex)
+ predSucceeds = pt.getPredicate().eval(self.parser, self._outerContext)
+ self._input.seek(currentPosition)
+ if predSucceeds:
+ c = ATNConfig(state=pt.target, config=config) # no pred context
+ else:
+ newSemCtx = andContext(config.semanticContext, pt.getPredicate())
+ c = ATNConfig(state=pt.target, semantic=newSemCtx, config=config)
+ else:
+ c = ATNConfig(state=pt.target, config=config)
+
+ if ParserATNSimulator.debug:
+ print("config from pred transition=" + str(c))
+ return c
+
+ def ruleTransition(self, config:ATNConfig, t:RuleTransition):
+ if ParserATNSimulator.debug:
+ print("CALL rule " + self.getRuleName(t.target.ruleIndex) + ", ctx=" + str(config.context))
+ returnState = t.followState
+ newContext = SingletonPredictionContext.create(config.context, returnState.stateNumber)
+ return ATNConfig(state=t.target, context=newContext, config=config )
+
+ def getConflictingAlts(self, configs:ATNConfigSet):
+ altsets = PredictionMode.getConflictingAltSubsets(configs)
+ return PredictionMode.getAlts(altsets)
+
+ # Sam pointed out a problem with the previous definition, v3, of
+ # ambiguous states. If we have another state associated with conflicting
+ # alternatives, we should keep going. For example, the following grammar
+ #
+ # s : (ID | ID ID?) ';' ;
+ #
+ # When the ATN simulation reaches the state before ';', it has a DFA
+ # state that looks like: [12|1|[], 6|2|[], 12|2|[]]. Naturally
+ # 12|1|[] and 12|2|[] conflict, but we cannot stop processing this node
+ # because alternative to has another way to continue, via [6|2|[]].
+ # The key is that we have a single state that has config's only associated
+ # with a single alternative, 2, and crucially the state transitions
+ # among the configurations are all non-epsilon transitions. That means
+ # we don't consider any conflicts that include alternative 2. So, we
+ # ignore the conflict between alts 1 and 2. We ignore a set of
+ # conflicting alts when there is an intersection with an alternative
+ # associated with a single alt state in the state→config-list map.
+ #
+ # It's also the case that we might have two conflicting configurations but
+ # also a 3rd nonconflicting configuration for a different alternative:
+ # [1|1|[], 1|2|[], 8|3|[]]. This can come about from grammar:
+ #
+ # a : A | A | A B ;
+ #
+ # After matching input A, we reach the stop state for rule A, state 1.
+ # State 8 is the state right before B. Clearly alternatives 1 and 2
+ # conflict and no amount of further lookahead will separate the two.
+ # However, alternative 3 will be able to continue and so we do not
+ # stop working on this state. In the previous example, we're concerned
+ # with states associated with the conflicting alternatives. Here alt
+ # 3 is not associated with the conflicting configs, but since we can continue
+ # looking for input reasonably, I don't declare the state done. We
+ # ignore a set of conflicting alts when we have an alternative
+ # that we still need to pursue.
+ #
+
+ def getConflictingAltsOrUniqueAlt(self, configs:ATNConfigSet):
+ conflictingAlts = None
+ if configs.uniqueAlt!= ATN.INVALID_ALT_NUMBER:
+ conflictingAlts = set()
+ conflictingAlts.add(configs.uniqueAlt)
+ else:
+ conflictingAlts = configs.conflictingAlts
+ return conflictingAlts
+
+ def getTokenName(self, t:int):
+ if t==Token.EOF:
+ return "EOF"
+ if self.parser is not None and \
+ self.parser.literalNames is not None and \
+ t < len(self.parser.literalNames):
+ return self.parser.literalNames[t] + "<" + str(t) + ">"
+ else:
+ return str(t)
+
+ def getLookaheadName(self, input:TokenStream):
+ return self.getTokenName(input.LA(1))
+
+ # Used for debugging in adaptivePredict around execATN but I cut
+ # it out for clarity now that alg. works well. We can leave this
+ # "dead" code for a bit.
+ #
+ def dumpDeadEndConfigs(self, nvae:NoViableAltException):
+ print("dead end configs: ")
+ for c in nvae.getDeadEndConfigs():
+ trans = "no edges"
+ if len(c.state.transitions)>0:
+ t = c.state.transitions[0]
+ if isinstance(t, AtomTransition):
+ trans = "Atom "+ self.getTokenName(t.label)
+ elif isinstance(t, SetTransition):
+ neg = isinstance(t, NotSetTransition)
+ trans = ("~" if neg else "")+"Set "+ str(t.set)
+ print(c.toString(self.parser, True) + ":" + trans, file=sys.stderr)
+
+ def noViableAlt(self, input:TokenStream, outerContext:ParserRuleContext, configs:ATNConfigSet, startIndex:int):
+ return NoViableAltException(self.parser, input, input.get(startIndex), input.LT(1), configs, outerContext)
+
+ def getUniqueAlt(self, configs:ATNConfigSet):
+ alt = ATN.INVALID_ALT_NUMBER
+ for c in configs:
+ if alt == ATN.INVALID_ALT_NUMBER:
+ alt = c.alt # found first alt
+ elif c.alt!=alt:
+ return ATN.INVALID_ALT_NUMBER
+ return alt
+
+ #
+ # Add an edge to the DFA, if possible. This method calls
+ # {@link #addDFAState} to ensure the {@code to} state is present in the
+ # DFA. If {@code from} is {@code null}, or if {@code t} is outside the
+ # range of edges that can be represented in the DFA tables, this method
+ # returns without adding the edge to the DFA.
+ #
+ #
If {@code to} is {@code null}, this method returns {@code null}.
+ # Otherwise, this method returns the {@link DFAState} returned by calling
+ # {@link #addDFAState} for the {@code to} state.
+ #
+ # @param dfa The DFA
+ # @param from The source state for the edge
+ # @param t The input symbol
+ # @param to The target state for the edge
+ #
+ # @return If {@code to} is {@code null}, this method returns {@code null};
+ # otherwise this method returns the result of calling {@link #addDFAState}
+ # on {@code to}
+ #
+ def addDFAEdge(self, dfa:DFA, from_:DFAState, t:int, to:DFAState):
+ if ParserATNSimulator.debug:
+ print("EDGE " + str(from_) + " -> " + str(to) + " upon " + self.getTokenName(t))
+
+ if to is None:
+ return None
+
+ to = self.addDFAState(dfa, to) # used existing if possible not incoming
+ if from_ is None or t < -1 or t > self.atn.maxTokenType:
+ return to
+
+ if from_.edges is None:
+ from_.edges = [None] * (self.atn.maxTokenType + 2)
+ from_.edges[t+1] = to # connect
+
+ if ParserATNSimulator.debug:
+ names = None if self.parser is None else self.parser.literalNames
+ print("DFA=\n" + dfa.toString(names))
+
+ return to
+
+ #
+ # Add state {@code D} to the DFA if it is not already present, and return
+ # the actual instance stored in the DFA. If a state equivalent to {@code D}
+ # is already in the DFA, the existing state is returned. Otherwise this
+ # method returns {@code D} after adding it to the DFA.
+ #
+ #
If {@code D} is {@link #ERROR}, this method returns {@link #ERROR} and
+ # does not change the DFA.
+ #
+ # @param dfa The dfa
+ # @param D The DFA state to add
+ # @return The state stored in the DFA. This will be either the existing
+ # state if {@code D} is already in the DFA, or {@code D} itself if the
+ # state was not already present.
+ #
+ def addDFAState(self, dfa:DFA, D:DFAState):
+ if D is self.ERROR:
+ return D
+
+
+ existing = dfa.states.get(D, None)
+ if existing is not None:
+ return existing
+
+ D.stateNumber = len(dfa.states)
+ if not D.configs.readonly:
+ D.configs.optimizeConfigs(self)
+ D.configs.setReadonly(True)
+ dfa.states[D] = D
+ if ParserATNSimulator.debug:
+ print("adding new DFA state: " + str(D))
+ return D
+
+ def reportAttemptingFullContext(self, dfa:DFA, conflictingAlts:set, configs:ATNConfigSet, startIndex:int, stopIndex:int):
+ if ParserATNSimulator.debug or ParserATNSimulator.retry_debug:
+ print("reportAttemptingFullContext decision=" + str(dfa.decision) + ":" + str(configs) +
+ ", input=" + self.parser.getTokenStream().getText(startIndex, stopIndex))
+ if self.parser is not None:
+ self.parser.getErrorListenerDispatch().reportAttemptingFullContext(self.parser, dfa, startIndex, stopIndex, conflictingAlts, configs)
+
+ def reportContextSensitivity(self, dfa:DFA, prediction:int, configs:ATNConfigSet, startIndex:int, stopIndex:int):
+ if ParserATNSimulator.debug or ParserATNSimulator.retry_debug:
+ print("reportContextSensitivity decision=" + str(dfa.decision) + ":" + str(configs) +
+ ", input=" + self.parser.getTokenStream().getText(startIndex, stopIndex))
+ if self.parser is not None:
+ self.parser.getErrorListenerDispatch().reportContextSensitivity(self.parser, dfa, startIndex, stopIndex, prediction, configs)
+
+ # If context sensitive parsing, we know it's ambiguity not conflict#
+ def reportAmbiguity(self, dfa:DFA, D:DFAState, startIndex:int, stopIndex:int,
+ exact:bool, ambigAlts:set, configs:ATNConfigSet ):
+ if ParserATNSimulator.debug or ParserATNSimulator.retry_debug:
+# ParserATNPathFinder finder = new ParserATNPathFinder(parser, atn);
+# int i = 1;
+# for (Transition t : dfa.atnStartState.transitions) {
+# print("ALT "+i+"=");
+# print(startIndex+".."+stopIndex+", len(input)="+parser.getInputStream().size());
+# TraceTree path = finder.trace(t.target, parser.getContext(), (TokenStream)parser.getInputStream(),
+# startIndex, stopIndex);
+# if ( path!=null ) {
+# print("path = "+path.toStringTree());
+# for (TraceTree leaf : path.leaves) {
+# List states = path.getPathToNode(leaf);
+# print("states="+states);
+# }
+# }
+# i++;
+# }
+ print("reportAmbiguity " + str(ambigAlts) + ":" + str(configs) +
+ ", input=" + self.parser.getTokenStream().getText(startIndex, stopIndex))
+ if self.parser is not None:
+ self.parser.getErrorListenerDispatch().reportAmbiguity(self.parser, dfa, startIndex, stopIndex, exact, ambigAlts, configs)
diff --git a/cameras/deps/antlr4/atn/PredictionMode.py b/cameras/deps/antlr4/atn/PredictionMode.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e5c73bb47f329d519f4e574ba3a36fc6c4ac29f
--- /dev/null
+++ b/cameras/deps/antlr4/atn/PredictionMode.py
@@ -0,0 +1,499 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+#
+# This enumeration defines the prediction modes available in ANTLR 4 along with
+# utility methods for analyzing configuration sets for conflicts and/or
+# ambiguities.
+
+
+from enum import Enum
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNConfig import ATNConfig
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.atn.ATNState import RuleStopState
+from antlr4.atn.SemanticContext import SemanticContext
+
+PredictionMode = None
+
+class PredictionMode(Enum):
+ #
+ # The SLL(*) prediction mode. This prediction mode ignores the current
+ # parser context when making predictions. This is the fastest prediction
+ # mode, and provides correct results for many grammars. This prediction
+ # mode is more powerful than the prediction mode provided by ANTLR 3, but
+ # may result in syntax errors for grammar and input combinations which are
+ # not SLL.
+ #
+ #
+ # When using this prediction mode, the parser will either return a correct
+ # parse tree (i.e. the same parse tree that would be returned with the
+ # {@link #LL} prediction mode), or it will report a syntax error. If a
+ # syntax error is encountered when using the {@link #SLL} prediction mode,
+ # it may be due to either an actual syntax error in the input or indicate
+ # that the particular combination of grammar and input requires the more
+ # powerful {@link #LL} prediction abilities to complete successfully.
+ #
+ #
+ # This prediction mode does not provide any guarantees for prediction
+ # behavior for syntactically-incorrect inputs.
+ #
+ SLL = 0
+ #
+ # The LL(*) prediction mode. This prediction mode allows the current parser
+ # context to be used for resolving SLL conflicts that occur during
+ # prediction. This is the fastest prediction mode that guarantees correct
+ # parse results for all combinations of grammars with syntactically correct
+ # inputs.
+ #
+ #
+ # When using this prediction mode, the parser will make correct decisions
+ # for all syntactically-correct grammar and input combinations. However, in
+ # cases where the grammar is truly ambiguous this prediction mode might not
+ # report a precise answer for exactly which alternatives are
+ # ambiguous.
+ #
+ #
+ # This prediction mode does not provide any guarantees for prediction
+ # behavior for syntactically-incorrect inputs.
+ #
+ LL = 1
+ #
+ # The LL(*) prediction mode with exact ambiguity detection. In addition to
+ # the correctness guarantees provided by the {@link #LL} prediction mode,
+ # this prediction mode instructs the prediction algorithm to determine the
+ # complete and exact set of ambiguous alternatives for every ambiguous
+ # decision encountered while parsing.
+ #
+ #
+ # This prediction mode may be used for diagnosing ambiguities during
+ # grammar development. Due to the performance overhead of calculating sets
+ # of ambiguous alternatives, this prediction mode should be avoided when
+ # the exact results are not necessary.
+ #
+ #
+ # This prediction mode does not provide any guarantees for prediction
+ # behavior for syntactically-incorrect inputs.
+ # This method computes the SLL prediction termination condition for both of
+ # the following cases.
+ #
+ #
+ #
The usual SLL+LL fallback upon SLL conflict
+ #
Pure SLL without LL fallback
+ #
+ #
+ #
COMBINED SLL+LL PARSING
+ #
+ #
When LL-fallback is enabled upon SLL conflict, correct predictions are
+ # ensured regardless of how the termination condition is computed by this
+ # method. Due to the substantially higher cost of LL prediction, the
+ # prediction should only fall back to LL when the additional lookahead
+ # cannot lead to a unique SLL prediction.
+ #
+ #
Assuming combined SLL+LL parsing, an SLL configuration set with only
+ # conflicting subsets should fall back to full LL, even if the
+ # configuration sets don't resolve to the same alternative (e.g.
+ # {@code {1,2}} and {@code {3,4}}. If there is at least one non-conflicting
+ # configuration, SLL could continue with the hopes that more lookahead will
+ # resolve via one of those non-conflicting configurations.
+ #
+ #
Here's the prediction termination rule them: SLL (for SLL+LL parsing)
+ # stops when it sees only conflicting configuration subsets. In contrast,
+ # full LL keeps going when there is uncertainty.
+ #
+ #
HEURISTIC
+ #
+ #
As a heuristic, we stop prediction when we see any conflicting subset
+ # unless we see a state that only has one alternative associated with it.
+ # The single-alt-state thing lets prediction continue upon rules like
+ # (otherwise, it would admit defeat too soon):
+ #
+ #
{@code [12|1|[], 6|2|[], 12|2|[]]. s : (ID | ID ID?) ';' ;}
+ #
+ #
When the ATN simulation reaches the state before {@code ';'}, it has a
+ # DFA state that looks like: {@code [12|1|[], 6|2|[], 12|2|[]]}. Naturally
+ # {@code 12|1|[]} and {@code 12|2|[]} conflict, but we cannot stop
+ # processing this node because alternative to has another way to continue,
+ # via {@code [6|2|[]]}.
+ #
+ #
It also let's us continue for this rule:
+ #
+ #
{@code [1|1|[], 1|2|[], 8|3|[]] a : A | A | A B ;}
+ #
+ #
After matching input A, we reach the stop state for rule A, state 1.
+ # State 8 is the state right before B. Clearly alternatives 1 and 2
+ # conflict and no amount of further lookahead will separate the two.
+ # However, alternative 3 will be able to continue and so we do not stop
+ # working on this state. In the previous example, we're concerned with
+ # states associated with the conflicting alternatives. Here alt 3 is not
+ # associated with the conflicting configs, but since we can continue
+ # looking for input reasonably, don't declare the state done.
+ #
+ #
PURE SLL PARSING
+ #
+ #
To handle pure SLL parsing, all we have to do is make sure that we
+ # combine stack contexts for configurations that differ only by semantic
+ # predicate. From there, we can do the usual SLL termination heuristic.
+ #
+ #
PREDICATES IN SLL+LL PARSING
+ #
+ #
SLL decisions don't evaluate predicates until after they reach DFA stop
+ # states because they need to create the DFA cache that works in all
+ # semantic situations. In contrast, full LL evaluates predicates collected
+ # during start state computation so it can ignore predicates thereafter.
+ # This means that SLL termination detection can totally ignore semantic
+ # predicates.
+ #
+ #
Implementation-wise, {@link ATNConfigSet} combines stack contexts but not
+ # semantic predicate contexts so we might see two configurations like the
+ # following.
+ #
+ #
{@code (s, 1, x, {}), (s, 1, x', {p})}
+ #
+ #
Before testing these configurations against others, we have to merge
+ # {@code x} and {@code x'} (without modifying the existing configurations).
+ # For example, we test {@code (x+x')==x''} when looking for conflicts in
+ # the following configurations.
If the configuration set has predicates (as indicated by
+ # {@link ATNConfigSet#hasSemanticContext}), this algorithm makes a copy of
+ # the configurations to strip out all of the predicates so that a standard
+ # {@link ATNConfigSet} will merge everything ignoring predicates.
+ #
+ @classmethod
+ def hasSLLConflictTerminatingPrediction(cls, mode:PredictionMode, configs:ATNConfigSet):
+ # Configs in rule stop states indicate reaching the end of the decision
+ # rule (local context) or end of start rule (full context). If all
+ # configs meet this condition, then none of the configurations is able
+ # to match additional input so we terminate prediction.
+ #
+ if cls.allConfigsInRuleStopStates(configs):
+ return True
+
+ # pure SLL mode parsing
+ if mode == PredictionMode.SLL:
+ # Don't bother with combining configs from different semantic
+ # contexts if we can fail over to full LL; costs more time
+ # since we'll often fail over anyway.
+ if configs.hasSemanticContext:
+ # dup configs, tossing out semantic predicates
+ dup = ATNConfigSet()
+ for c in configs:
+ c = ATNConfig(config=c, semantic=SemanticContext.NONE)
+ dup.add(c)
+ configs = dup
+ # now we have combined contexts for configs with dissimilar preds
+
+ # pure SLL or combined SLL+LL mode parsing
+ altsets = cls.getConflictingAltSubsets(configs)
+ return cls.hasConflictingAltSet(altsets) and not cls.hasStateAssociatedWithOneAlt(configs)
+
+ # Checks if any configuration in {@code configs} is in a
+ # {@link RuleStopState}. Configurations meeting this condition have reached
+ # the end of the decision rule (local context) or end of start rule (full
+ # context).
+ #
+ # @param configs the configuration set to test
+ # @return {@code true} if any configuration in {@code configs} is in a
+ # {@link RuleStopState}, otherwise {@code false}
+ @classmethod
+ def hasConfigInRuleStopState(cls, configs:ATNConfigSet):
+ return any(isinstance(cfg.state, RuleStopState) for cfg in configs)
+
+ # Checks if all configurations in {@code configs} are in a
+ # {@link RuleStopState}. Configurations meeting this condition have reached
+ # the end of the decision rule (local context) or end of start rule (full
+ # context).
+ #
+ # @param configs the configuration set to test
+ # @return {@code true} if all configurations in {@code configs} are in a
+ # {@link RuleStopState}, otherwise {@code false}
+ @classmethod
+ def allConfigsInRuleStopStates(cls, configs:ATNConfigSet):
+ return all(isinstance(cfg.state, RuleStopState) for cfg in configs)
+
+ #
+ # Full LL prediction termination.
+ #
+ #
Can we stop looking ahead during ATN simulation or is there some
+ # uncertainty as to which alternative we will ultimately pick, after
+ # consuming more input? Even if there are partial conflicts, we might know
+ # that everything is going to resolve to the same minimum alternative. That
+ # means we can stop since no more lookahead will change that fact. On the
+ # other hand, there might be multiple conflicts that resolve to different
+ # minimums. That means we need more look ahead to decide which of those
+ # alternatives we should predict.
+ #
+ #
The basic idea is to split the set of configurations {@code C}, into
+ # conflicting subsets {@code (s, _, ctx, _)} and singleton subsets with
+ # non-conflicting configurations. Two configurations conflict if they have
+ # identical {@link ATNConfig#state} and {@link ATNConfig#context} values
+ # but different {@link ATNConfig#alt} value, e.g. {@code (s, i, ctx, _)}
+ # and {@code (s, j, ctx, _)} for {@code i!=j}.
+ #
+ #
Reduce these configuration subsets to the set of possible alternatives.
+ # You can compute the alternative subsets in one pass as follows:
+ #
+ #
{@code A_s,ctx = {i | (s, i, ctx, _)}} for each configuration in
+ # {@code C} holding {@code s} and {@code ctx} fixed.
+ #
+ #
Or in pseudo-code, for each configuration {@code c} in {@code C}:
+ #
+ #
+ # map[c] U= c.{@link ATNConfig#alt alt} # map hash/equals uses s and x, not
+ # alt and not pred
+ #
+ #
+ #
The values in {@code map} are the set of {@code A_s,ctx} sets.
+ #
+ #
If {@code |A_s,ctx|=1} then there is no conflict associated with
+ # {@code s} and {@code ctx}.
+ #
+ #
Reduce the subsets to singletons by choosing a minimum of each subset. If
+ # the union of these alternative subsets is a singleton, then no amount of
+ # more lookahead will help us. We will always pick that alternative. If,
+ # however, there is more than one alternative, then we are uncertain which
+ # alternative to predict and must continue looking for resolution. We may
+ # or may not discover an ambiguity in the future, even if there are no
+ # conflicting subsets this round.
+ #
+ #
The biggest sin is to terminate early because it means we've made a
+ # decision but were uncertain as to the eventual outcome. We haven't used
+ # enough lookahead. On the other hand, announcing a conflict too late is no
+ # big deal; you will still have the conflict. It's just inefficient. It
+ # might even look until the end of file.
+ #
+ #
No special consideration for semantic predicates is required because
+ # predicates are evaluated on-the-fly for full LL prediction, ensuring that
+ # no configuration contains a semantic context during the termination
+ # check.
+ #
+ #
CONFLICTING CONFIGS
+ #
+ #
Two configurations {@code (s, i, x)} and {@code (s, j, x')}, conflict
+ # when {@code i!=j} but {@code x=x'}. Because we merge all
+ # {@code (s, i, _)} configurations together, that means that there are at
+ # most {@code n} configurations associated with state {@code s} for
+ # {@code n} possible alternatives in the decision. The merged stacks
+ # complicate the comparison of configuration contexts {@code x} and
+ # {@code x'}. Sam checks to see if one is a subset of the other by calling
+ # merge and checking to see if the merged result is either {@code x} or
+ # {@code x'}. If the {@code x} associated with lowest alternative {@code i}
+ # is the superset, then {@code i} is the only possible prediction since the
+ # others resolve to {@code min(i)} as well. However, if {@code x} is
+ # associated with {@code j>i} then at least one stack configuration for
+ # {@code j} is not in conflict with alternative {@code i}. The algorithm
+ # should keep going, looking for more lookahead due to the uncertainty.
+ #
+ #
For simplicity, I'm doing a equality check between {@code x} and
+ # {@code x'} that lets the algorithm continue to consume lookahead longer
+ # than necessary. The reason I like the equality is of course the
+ # simplicity but also because that is the test you need to detect the
+ # alternatives that are actually in conflict.
+ #
+ #
CONTINUE/STOP RULE
+ #
+ #
Continue if union of resolved alternative sets from non-conflicting and
+ # conflicting alternative subsets has more than one alternative. We are
+ # uncertain about which alternative to predict.
+ #
+ #
The complete set of alternatives, {@code [i for (_,i,_)]}, tells us which
+ # alternatives are still in the running for the amount of input we've
+ # consumed at this point. The conflicting sets let us to strip away
+ # configurations that won't lead to more states because we resolve
+ # conflicts to the configuration with a minimum alternate for the
+ # conflicting set.
+ #
+ #
CASES
+ #
+ #
+ #
+ #
no conflicts and more than 1 alternative in set => continue
If all states report the same conflicting set of alternatives, then we
+ # know we have the exact ambiguity set.
+ #
+ #
|A_i|>1 and
+ # A_i = A_j for all i, j.
+ #
+ #
In other words, we continue examining lookahead until all {@code A_i}
+ # have more than one alternative and all {@code A_i} are the same. If
+ # {@code A={{1,2}, {1,3}}}, then regular LL prediction would terminate
+ # because the resolved set is {@code {1}}. To determine what the real
+ # ambiguity is, we have to know whether the ambiguity is between one and
+ # two or one and three so we keep going. We can only stop prediction when
+ # we need exact ambiguity detection when the sets look like
+ # {@code A={{1,2}}} or {@code {{1,2},{1,2}}}, etc...
+ #
+ @classmethod
+ def resolvesToJustOneViableAlt(cls, altsets:list):
+ return cls.getSingleViableAlt(altsets)
+
+ #
+ # Determines if every alternative subset in {@code altsets} contains more
+ # than one alternative.
+ #
+ # @param altsets a collection of alternative subsets
+ # @return {@code true} if every {@link BitSet} in {@code altsets} has
+ # {@link BitSet#cardinality cardinality} > 1, otherwise {@code false}
+ #
+ @classmethod
+ def allSubsetsConflict(cls, altsets:list):
+ return not cls.hasNonConflictingAltSet(altsets)
+
+ #
+ # Determines if any single alternative subset in {@code altsets} contains
+ # exactly one alternative.
+ #
+ # @param altsets a collection of alternative subsets
+ # @return {@code true} if {@code altsets} contains a {@link BitSet} with
+ # {@link BitSet#cardinality cardinality} 1, otherwise {@code false}
+ #
+ @classmethod
+ def hasNonConflictingAltSet(cls, altsets:list):
+ return any(len(alts) == 1 for alts in altsets)
+
+ #
+ # Determines if any single alternative subset in {@code altsets} contains
+ # more than one alternative.
+ #
+ # @param altsets a collection of alternative subsets
+ # @return {@code true} if {@code altsets} contains a {@link BitSet} with
+ # {@link BitSet#cardinality cardinality} > 1, otherwise {@code false}
+ #
+ @classmethod
+ def hasConflictingAltSet(cls, altsets:list):
+ return any(len(alts) > 1 for alts in altsets)
+
+ #
+ # Determines if every alternative subset in {@code altsets} is equivalent.
+ #
+ # @param altsets a collection of alternative subsets
+ # @return {@code true} if every member of {@code altsets} is equal to the
+ # others, otherwise {@code false}
+ #
+ @classmethod
+ def allSubsetsEqual(cls, altsets:list):
+ if not altsets:
+ return True
+ first = next(iter(altsets))
+ return all(alts == first for alts in iter(altsets))
+
+ #
+ # Returns the unique alternative predicted by all alternative subsets in
+ # {@code altsets}. If no such alternative exists, this method returns
+ # {@link ATN#INVALID_ALT_NUMBER}.
+ #
+ # @param altsets a collection of alternative subsets
+ #
+ @classmethod
+ def getUniqueAlt(cls, altsets:list):
+ all = cls.getAlts(altsets)
+ if len(all)==1:
+ return next(iter(all))
+ return ATN.INVALID_ALT_NUMBER
+
+ # Gets the complete set of represented alternatives for a collection of
+ # alternative subsets. This method returns the union of each {@link BitSet}
+ # in {@code altsets}.
+ #
+ # @param altsets a collection of alternative subsets
+ # @return the set of represented alternatives in {@code altsets}
+ #
+ @classmethod
+ def getAlts(cls, altsets:list):
+ return set.union(*altsets)
+
+ #
+ # This function gets the conflicting alt subsets from a configuration set.
+ # For each configuration {@code c} in {@code configs}:
+ #
+ #
+ # map[c] U= c.{@link ATNConfig#alt alt} # map hash/equals uses s and x, not
+ # alt and not pred
+ #
+ #
+ @classmethod
+ def getConflictingAltSubsets(cls, configs:ATNConfigSet):
+ configToAlts = dict()
+ for c in configs:
+ h = hash((c.state.stateNumber, c.context))
+ alts = configToAlts.get(h, None)
+ if alts is None:
+ alts = set()
+ configToAlts[h] = alts
+ alts.add(c.alt)
+ return configToAlts.values()
+
+ #
+ # Get a map from state to alt subset from a configuration set. For each
+ # configuration {@code c} in {@code configs}:
+ #
+ #
+ #
+ @classmethod
+ def getStateToAltMap(cls, configs:ATNConfigSet):
+ m = dict()
+ for c in configs:
+ alts = m.get(c.state, None)
+ if alts is None:
+ alts = set()
+ m[c.state] = alts
+ alts.add(c.alt)
+ return m
+
+ @classmethod
+ def hasStateAssociatedWithOneAlt(cls, configs:ATNConfigSet):
+ return any(len(alts) == 1 for alts in cls.getStateToAltMap(configs).values())
+
+ @classmethod
+ def getSingleViableAlt(cls, altsets:list):
+ viableAlts = set()
+ for alts in altsets:
+ minAlt = min(alts)
+ viableAlts.add(minAlt)
+ if len(viableAlts)>1 : # more than 1 viable alt
+ return ATN.INVALID_ALT_NUMBER
+ return min(viableAlts)
diff --git a/cameras/deps/antlr4/atn/SemanticContext.py b/cameras/deps/antlr4/atn/SemanticContext.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f4dc31088d35b73304432c46c54e31c1ab92700
--- /dev/null
+++ b/cameras/deps/antlr4/atn/SemanticContext.py
@@ -0,0 +1,323 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+# A tree structure used to record the semantic context in which
+# an ATN configuration is valid. It's either a single predicate,
+# a conjunction {@code p1&&p2}, or a sum of products {@code p1||p2}.
+#
+#
I have scoped the {@link AND}, {@link OR}, and {@link Predicate} subclasses of
+# {@link SemanticContext} within the scope of this outer class.
+#
+from antlr4.Recognizer import Recognizer
+from antlr4.RuleContext import RuleContext
+from io import StringIO
+
+
+class SemanticContext(object):
+ #
+ # The default {@link SemanticContext}, which is semantically equivalent to
+ # a predicate of the form {@code {true}?}.
+ #
+ NONE = None
+
+ #
+ # For context independent predicates, we evaluate them without a local
+ # context (i.e., null context). That way, we can evaluate them without
+ # having to create proper rule-specific context during prediction (as
+ # opposed to the parser, which creates them naturally). In a practical
+ # sense, this avoids a cast exception from RuleContext to myruleContext.
+ #
+ #
For context dependent predicates, we must pass in a local context so that
+ # references such as $arg evaluate properly as _localctx.arg. We only
+ # capture context dependent predicates in the context in which we begin
+ # prediction, so we passed in the outer context here in case of context
+ # dependent predicate evaluation.
+ #
+ def eval(self, parser:Recognizer , outerContext:RuleContext ):
+ pass
+
+ #
+ # Evaluate the precedence predicates for the context and reduce the result.
+ #
+ # @param parser The parser instance.
+ # @param outerContext The current parser context object.
+ # @return The simplified semantic context after precedence predicates are
+ # evaluated, which will be one of the following values.
+ #
+ #
{@link #NONE}: if the predicate simplifies to {@code true} after
+ # precedence predicates are evaluated.
+ #
{@code null}: if the predicate simplifies to {@code false} after
+ # precedence predicates are evaluated.
+ #
{@code this}: if the semantic context is not changed as a result of
+ # precedence predicate evaluation.
+ #
A non-{@code null} {@link SemanticContext}: the new simplified
+ # semantic context after precedence predicates are evaluated.
+ #
+ #
+ def evalPrecedence(self, parser:Recognizer, outerContext:RuleContext):
+ return self
+
+# need forward declaration
+AND = None
+
+def andContext(a:SemanticContext, b:SemanticContext):
+ if a is None or a is SemanticContext.NONE:
+ return b
+ if b is None or b is SemanticContext.NONE:
+ return a
+ result = AND(a, b)
+ if len(result.opnds) == 1:
+ return result.opnds[0]
+ else:
+ return result
+
+# need forward declaration
+OR = None
+
+def orContext(a:SemanticContext, b:SemanticContext):
+ if a is None:
+ return b
+ if b is None:
+ return a
+ if a is SemanticContext.NONE or b is SemanticContext.NONE:
+ return SemanticContext.NONE
+ result = OR(a, b)
+ if len(result.opnds) == 1:
+ return result.opnds[0]
+ else:
+ return result
+
+def filterPrecedencePredicates(collection:set):
+ return [context for context in collection if isinstance(context, PrecedencePredicate)]
+
+
+class Predicate(SemanticContext):
+ __slots__ = ('ruleIndex', 'predIndex', 'isCtxDependent')
+
+ def __init__(self, ruleIndex:int=-1, predIndex:int=-1, isCtxDependent:bool=False):
+ self.ruleIndex = ruleIndex
+ self.predIndex = predIndex
+ self.isCtxDependent = isCtxDependent # e.g., $i ref in pred
+
+ def eval(self, parser:Recognizer , outerContext:RuleContext ):
+ localctx = outerContext if self.isCtxDependent else None
+ return parser.sempred(localctx, self.ruleIndex, self.predIndex)
+
+ def __hash__(self):
+ return hash((self.ruleIndex, self.predIndex, self.isCtxDependent))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, Predicate):
+ return False
+ return self.ruleIndex == other.ruleIndex and \
+ self.predIndex == other.predIndex and \
+ self.isCtxDependent == other.isCtxDependent
+
+ def __str__(self):
+ return "{" + str(self.ruleIndex) + ":" + str(self.predIndex) + "}?"
+
+
+class PrecedencePredicate(SemanticContext):
+
+ def __init__(self, precedence:int=0):
+ self.precedence = precedence
+
+ def eval(self, parser:Recognizer , outerContext:RuleContext ):
+ return parser.precpred(outerContext, self.precedence)
+
+ def evalPrecedence(self, parser:Recognizer, outerContext:RuleContext):
+ if parser.precpred(outerContext, self.precedence):
+ return SemanticContext.NONE
+ else:
+ return None
+
+ def __lt__(self, other):
+ return self.precedence < other.precedence
+
+ def __hash__(self):
+ return 31
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, PrecedencePredicate):
+ return False
+ else:
+ return self.precedence == other.precedence
+
+# A semantic context which is true whenever none of the contained contexts
+# is false.
+del AND
+class AND(SemanticContext):
+ __slots__ = 'opnds'
+
+ def __init__(self, a:SemanticContext, b:SemanticContext):
+ operands = set()
+ if isinstance( a, AND ):
+ operands.update(a.opnds)
+ else:
+ operands.add(a)
+ if isinstance( b, AND ):
+ operands.update(b.opnds)
+ else:
+ operands.add(b)
+
+ precedencePredicates = filterPrecedencePredicates(operands)
+ if len(precedencePredicates)>0:
+ # interested in the transition with the lowest precedence
+ reduced = min(precedencePredicates)
+ operands.add(reduced)
+
+ self.opnds = list(operands)
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, AND):
+ return False
+ else:
+ return self.opnds == other.opnds
+
+ def __hash__(self):
+ h = 0
+ for o in self.opnds:
+ h = hash((h, o))
+ return hash((h, "AND"))
+
+ #
+ # {@inheritDoc}
+ #
+ #
+ # The evaluation of predicates by this context is short-circuiting, but
+ # unordered.
+ #
+ def eval(self, parser:Recognizer, outerContext:RuleContext):
+ return all(opnd.eval(parser, outerContext) for opnd in self.opnds)
+
+ def evalPrecedence(self, parser:Recognizer, outerContext:RuleContext):
+ differs = False
+ operands = []
+ for context in self.opnds:
+ evaluated = context.evalPrecedence(parser, outerContext)
+ differs |= evaluated is not context
+ if evaluated is None:
+ # The AND context is false if any element is false
+ return None
+ elif evaluated is not SemanticContext.NONE:
+ # Reduce the result by skipping true elements
+ operands.append(evaluated)
+
+ if not differs:
+ return self
+
+ if len(operands)==0:
+ # all elements were true, so the AND context is true
+ return SemanticContext.NONE
+
+ result = None
+ for o in operands:
+ result = o if result is None else andContext(result, o)
+
+ return result
+
+ def __str__(self):
+ with StringIO() as buf:
+ first = True
+ for o in self.opnds:
+ if not first:
+ buf.write("&&")
+ buf.write(str(o))
+ first = False
+ return buf.getvalue()
+
+#
+# A semantic context which is true whenever at least one of the contained
+# contexts is true.
+del OR
+class OR (SemanticContext):
+ __slots__ = 'opnds'
+
+ def __init__(self, a:SemanticContext, b:SemanticContext):
+ operands = set()
+ if isinstance( a, OR ):
+ operands.update(a.opnds)
+ else:
+ operands.add(a)
+ if isinstance( b, OR ):
+ operands.update(b.opnds)
+ else:
+ operands.add(b)
+
+ precedencePredicates = filterPrecedencePredicates(operands)
+ if len(precedencePredicates)>0:
+ # interested in the transition with the highest precedence
+ s = sorted(precedencePredicates)
+ reduced = s[-1]
+ operands.add(reduced)
+
+ self.opnds = list(operands)
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, OR):
+ return False
+ else:
+ return self.opnds == other.opnds
+
+ def __hash__(self):
+ h = 0
+ for o in self.opnds:
+ h = hash((h, o))
+ return hash((h, "OR"))
+
+ #
+ # The evaluation of predicates by this context is short-circuiting, but
+ # unordered.
+ #
+ def eval(self, parser:Recognizer, outerContext:RuleContext):
+ return any(opnd.eval(parser, outerContext) for opnd in self.opnds)
+
+ def evalPrecedence(self, parser:Recognizer, outerContext:RuleContext):
+ differs = False
+ operands = []
+ for context in self.opnds:
+ evaluated = context.evalPrecedence(parser, outerContext)
+ differs |= evaluated is not context
+ if evaluated is SemanticContext.NONE:
+ # The OR context is true if any element is true
+ return SemanticContext.NONE
+ elif evaluated is not None:
+ # Reduce the result by skipping false elements
+ operands.append(evaluated)
+
+ if not differs:
+ return self
+
+ if len(operands)==0:
+ # all elements were false, so the OR context is false
+ return None
+
+ result = None
+ for o in operands:
+ result = o if result is None else orContext(result, o)
+
+ return result
+
+ def __str__(self):
+ with StringIO() as buf:
+ first = True
+ for o in self.opnds:
+ if not first:
+ buf.write("||")
+ buf.write(str(o))
+ first = False
+ return buf.getvalue()
+
+
+SemanticContext.NONE = Predicate()
diff --git a/cameras/deps/antlr4/atn/Transition.py b/cameras/deps/antlr4/atn/Transition.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e4c9971763c34dbb2690660434c5c99d44193e1
--- /dev/null
+++ b/cameras/deps/antlr4/atn/Transition.py
@@ -0,0 +1,268 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+# An ATN transition between any two ATN states. Subclasses define
+# atom, set, epsilon, action, predicate, rule transitions.
+#
+#
This is a one way link. It emanates from a state (usually via a list of
+# transitions) and has a target state.
+#
+#
Since we never have to change the ATN transitions once we construct it,
+# we can fix these transitions as specific classes. The DFA transitions
+# on the other hand need to update the labels as it adds transitions to
+# the states. We'll use the term Edge for the DFA to distinguish them from
+# ATN transitions.
+#
+from antlr4.IntervalSet import IntervalSet
+from antlr4.Token import Token
+
+# need forward declarations
+from antlr4.atn.SemanticContext import Predicate, PrecedencePredicate
+
+ATNState = None
+RuleStartState = None
+
+class Transition (object):
+ __slots__ = ('target','isEpsilon','label')
+
+ # constants for serialization
+ EPSILON = 1
+ RANGE = 2
+ RULE = 3
+ PREDICATE = 4 # e.g., {isType(input.LT(1))}?
+ ATOM = 5
+ ACTION = 6
+ SET = 7 # ~(A|B) or ~atom, wildcard, which convert to next 2
+ NOT_SET = 8
+ WILDCARD = 9
+ PRECEDENCE = 10
+
+ serializationNames = [
+ "INVALID",
+ "EPSILON",
+ "RANGE",
+ "RULE",
+ "PREDICATE",
+ "ATOM",
+ "ACTION",
+ "SET",
+ "NOT_SET",
+ "WILDCARD",
+ "PRECEDENCE"
+ ]
+
+ serializationTypes = dict()
+
+ def __init__(self, target:ATNState):
+ # The target of this transition.
+ if target is None:
+ raise Exception("target cannot be null.")
+ self.target = target
+ # Are we epsilon, action, sempred?
+ self.isEpsilon = False
+ self.label = None
+
+
+# TODO: make all transitions sets? no, should remove set edges
+class AtomTransition(Transition):
+ __slots__ = ('label_', 'serializationType')
+
+ def __init__(self, target:ATNState, label:int):
+ super().__init__(target)
+ self.label_ = label # The token type or character value; or, signifies special label.
+ self.label = self.makeLabel()
+ self.serializationType = self.ATOM
+
+ def makeLabel(self):
+ s = IntervalSet()
+ s.addOne(self.label_)
+ return s
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return self.label_ == symbol
+
+ def __str__(self):
+ return str(self.label_)
+
+class RuleTransition(Transition):
+ __slots__ = ('ruleIndex', 'precedence', 'followState', 'serializationType')
+
+ def __init__(self, ruleStart:RuleStartState, ruleIndex:int, precedence:int, followState:ATNState):
+ super().__init__(ruleStart)
+ self.ruleIndex = ruleIndex # ptr to the rule definition object for this rule ref
+ self.precedence = precedence
+ self.followState = followState # what node to begin computations following ref to rule
+ self.serializationType = self.RULE
+ self.isEpsilon = True
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return False
+
+
+class EpsilonTransition(Transition):
+ __slots__ = ('serializationType', 'outermostPrecedenceReturn')
+
+ def __init__(self, target, outermostPrecedenceReturn=-1):
+ super(EpsilonTransition, self).__init__(target)
+ self.serializationType = self.EPSILON
+ self.isEpsilon = True
+ self.outermostPrecedenceReturn = outermostPrecedenceReturn
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return False
+
+ def __str__(self):
+ return "epsilon"
+
+class RangeTransition(Transition):
+ __slots__ = ('serializationType', 'start', 'stop')
+
+ def __init__(self, target:ATNState, start:int, stop:int):
+ super().__init__(target)
+ self.serializationType = self.RANGE
+ self.start = start
+ self.stop = stop
+ self.label = self.makeLabel()
+
+ def makeLabel(self):
+ s = IntervalSet()
+ s.addRange(range(self.start, self.stop + 1))
+ return s
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return symbol >= self.start and symbol <= self.stop
+
+ def __str__(self):
+ return "'" + chr(self.start) + "'..'" + chr(self.stop) + "'"
+
+class AbstractPredicateTransition(Transition):
+
+ def __init__(self, target:ATNState):
+ super().__init__(target)
+
+
+class PredicateTransition(AbstractPredicateTransition):
+ __slots__ = ('serializationType', 'ruleIndex', 'predIndex', 'isCtxDependent')
+
+ def __init__(self, target:ATNState, ruleIndex:int, predIndex:int, isCtxDependent:bool):
+ super().__init__(target)
+ self.serializationType = self.PREDICATE
+ self.ruleIndex = ruleIndex
+ self.predIndex = predIndex
+ self.isCtxDependent = isCtxDependent # e.g., $i ref in pred
+ self.isEpsilon = True
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return False
+
+ def getPredicate(self):
+ return Predicate(self.ruleIndex, self.predIndex, self.isCtxDependent)
+
+ def __str__(self):
+ return "pred_" + str(self.ruleIndex) + ":" + str(self.predIndex)
+
+class ActionTransition(Transition):
+ __slots__ = ('serializationType', 'ruleIndex', 'actionIndex', 'isCtxDependent')
+
+ def __init__(self, target:ATNState, ruleIndex:int, actionIndex:int=-1, isCtxDependent:bool=False):
+ super().__init__(target)
+ self.serializationType = self.ACTION
+ self.ruleIndex = ruleIndex
+ self.actionIndex = actionIndex
+ self.isCtxDependent = isCtxDependent # e.g., $i ref in pred
+ self.isEpsilon = True
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return False
+
+ def __str__(self):
+ return "action_"+self.ruleIndex+":"+self.actionIndex
+
+# A transition containing a set of values.
+class SetTransition(Transition):
+ __slots__ = 'serializationType'
+
+ def __init__(self, target:ATNState, set:IntervalSet):
+ super().__init__(target)
+ self.serializationType = self.SET
+ if set is not None:
+ self.label = set
+ else:
+ self.label = IntervalSet()
+ self.label.addRange(range(Token.INVALID_TYPE, Token.INVALID_TYPE + 1))
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return symbol in self.label
+
+ def __str__(self):
+ return str(self.label)
+
+class NotSetTransition(SetTransition):
+
+ def __init__(self, target:ATNState, set:IntervalSet):
+ super().__init__(target, set)
+ self.serializationType = self.NOT_SET
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return symbol >= minVocabSymbol \
+ and symbol <= maxVocabSymbol \
+ and not super(type(self), self).matches(symbol, minVocabSymbol, maxVocabSymbol)
+
+ def __str__(self):
+ return '~' + super(type(self), self).__str__()
+
+
+class WildcardTransition(Transition):
+ __slots__ = 'serializationType'
+
+ def __init__(self, target:ATNState):
+ super().__init__(target)
+ self.serializationType = self.WILDCARD
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return symbol >= minVocabSymbol and symbol <= maxVocabSymbol
+
+ def __str__(self):
+ return "."
+
+
+class PrecedencePredicateTransition(AbstractPredicateTransition):
+ __slots__ = ('serializationType', 'precedence')
+
+ def __init__(self, target:ATNState, precedence:int):
+ super().__init__(target)
+ self.serializationType = self.PRECEDENCE
+ self.precedence = precedence
+ self.isEpsilon = True
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return False
+
+
+ def getPredicate(self):
+ return PrecedencePredicate(self.precedence)
+
+ def __str__(self):
+ return self.precedence + " >= _p"
+
+
+Transition.serializationTypes = {
+ EpsilonTransition: Transition.EPSILON,
+ RangeTransition: Transition.RANGE,
+ RuleTransition: Transition.RULE,
+ PredicateTransition: Transition.PREDICATE,
+ AtomTransition: Transition.ATOM,
+ ActionTransition: Transition.ACTION,
+ SetTransition: Transition.SET,
+ NotSetTransition: Transition.NOT_SET,
+ WildcardTransition: Transition.WILDCARD,
+ PrecedencePredicateTransition: Transition.PRECEDENCE
+ }
+
+del ATNState
+del RuleStartState
+
+from antlr4.atn.ATNState import *
diff --git a/cameras/deps/antlr4/atn/__init__.py b/cameras/deps/antlr4/atn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..216c000dc5ffc8e53cc9c596e420c1e67604d1aa
--- /dev/null
+++ b/cameras/deps/antlr4/atn/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ericvergnaud'
diff --git a/cameras/deps/antlr4/dfa/DFA.py b/cameras/deps/antlr4/dfa/DFA.py
new file mode 100644
index 0000000000000000000000000000000000000000..d80589a6834a48cc6bb718bf33f57156e32ec934
--- /dev/null
+++ b/cameras/deps/antlr4/dfa/DFA.py
@@ -0,0 +1,133 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+from antlr4.atn.ATNState import StarLoopEntryState
+
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.atn.ATNState import DecisionState
+from antlr4.dfa.DFAState import DFAState
+from antlr4.error.Errors import IllegalStateException
+
+
+class DFA(object):
+ __slots__ = ('atnStartState', 'decision', '_states', 's0', 'precedenceDfa')
+
+ def __init__(self, atnStartState:DecisionState, decision:int=0):
+ # From which ATN state did we create this DFA?
+ self.atnStartState = atnStartState
+ self.decision = decision
+ # A set of all DFA states. Use {@link Map} so we can get old state back
+ # ({@link Set} only allows you to see if it's there).
+ self._states = dict()
+ self.s0 = None
+ # {@code true} if this DFA is for a precedence decision; otherwise,
+ # {@code false}. This is the backing field for {@link #isPrecedenceDfa},
+ # {@link #setPrecedenceDfa}.
+ self.precedenceDfa = False
+
+ if isinstance(atnStartState, StarLoopEntryState):
+ if atnStartState.isPrecedenceDecision:
+ self.precedenceDfa = True
+ precedenceState = DFAState(configs=ATNConfigSet())
+ precedenceState.edges = []
+ precedenceState.isAcceptState = False
+ precedenceState.requiresFullContext = False
+ self.s0 = precedenceState
+
+
+ # Get the start state for a specific precedence value.
+ #
+ # @param precedence The current precedence.
+ # @return The start state corresponding to the specified precedence, or
+ # {@code null} if no start state exists for the specified precedence.
+ #
+ # @throws IllegalStateException if this is not a precedence DFA.
+ # @see #isPrecedenceDfa()
+
+ def getPrecedenceStartState(self, precedence:int):
+ if not self.precedenceDfa:
+ raise IllegalStateException("Only precedence DFAs may contain a precedence start state.")
+
+ # s0.edges is never null for a precedence DFA
+ if precedence < 0 or precedence >= len(self.s0.edges):
+ return None
+ return self.s0.edges[precedence]
+
+ # Set the start state for a specific precedence value.
+ #
+ # @param precedence The current precedence.
+ # @param startState The start state corresponding to the specified
+ # precedence.
+ #
+ # @throws IllegalStateException if this is not a precedence DFA.
+ # @see #isPrecedenceDfa()
+ #
+ def setPrecedenceStartState(self, precedence:int, startState:DFAState):
+ if not self.precedenceDfa:
+ raise IllegalStateException("Only precedence DFAs may contain a precedence start state.")
+
+ if precedence < 0:
+ return
+
+ # synchronization on s0 here is ok. when the DFA is turned into a
+ # precedence DFA, s0 will be initialized once and not updated again
+ # s0.edges is never null for a precedence DFA
+ if precedence >= len(self.s0.edges):
+ ext = [None] * (precedence + 1 - len(self.s0.edges))
+ self.s0.edges.extend(ext)
+ self.s0.edges[precedence] = startState
+ #
+ # Sets whether this is a precedence DFA. If the specified value differs
+ # from the current DFA configuration, the following actions are taken;
+ # otherwise no changes are made to the current DFA.
+ #
+ #
+ #
The {@link #states} map is cleared
+ #
If {@code precedenceDfa} is {@code false}, the initial state
+ # {@link #s0} is set to {@code null}; otherwise, it is initialized to a new
+ # {@link DFAState} with an empty outgoing {@link DFAState#edges} array to
+ # store the start states for individual precedence values.
+ #
The {@link #precedenceDfa} field is updated
+ #
+ #
+ # @param precedenceDfa {@code true} if this is a precedence DFA; otherwise,
+ # {@code false}
+
+ def setPrecedenceDfa(self, precedenceDfa:bool):
+ if self.precedenceDfa != precedenceDfa:
+ self._states = dict()
+ if precedenceDfa:
+ precedenceState = DFAState(configs=ATNConfigSet())
+ precedenceState.edges = []
+ precedenceState.isAcceptState = False
+ precedenceState.requiresFullContext = False
+ self.s0 = precedenceState
+ else:
+ self.s0 = None
+ self.precedenceDfa = precedenceDfa
+
+ @property
+ def states(self):
+ return self._states
+
+ # Return a list of all states in this DFA, ordered by state number.
+ def sortedStates(self):
+ return sorted(self._states.keys(), key=lambda state: state.stateNumber)
+
+ def __str__(self):
+ return self.toString(None)
+
+ def toString(self, literalNames:list=None, symbolicNames:list=None):
+ if self.s0 is None:
+ return ""
+ from antlr4.dfa.DFASerializer import DFASerializer
+ serializer = DFASerializer(self,literalNames,symbolicNames)
+ return str(serializer)
+
+ def toLexerString(self):
+ if self.s0 is None:
+ return ""
+ from antlr4.dfa.DFASerializer import LexerDFASerializer
+ serializer = LexerDFASerializer(self)
+ return str(serializer)
diff --git a/cameras/deps/antlr4/dfa/DFASerializer.py b/cameras/deps/antlr4/dfa/DFASerializer.py
new file mode 100644
index 0000000000000000000000000000000000000000..bca0727b76dc54909be0bf60b6d636ec8f539927
--- /dev/null
+++ b/cameras/deps/antlr4/dfa/DFASerializer.py
@@ -0,0 +1,73 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+# A DFA walker that knows how to dump them to serialized strings.#/
+from io import StringIO
+from antlr4 import DFA
+from antlr4.Utils import str_list
+from antlr4.dfa.DFAState import DFAState
+
+
+class DFASerializer(object):
+ __slots__ = ('dfa', 'literalNames', 'symbolicNames')
+
+ def __init__(self, dfa:DFA, literalNames:list=None, symbolicNames:list=None):
+ self.dfa = dfa
+ self.literalNames = literalNames
+ self.symbolicNames = symbolicNames
+
+ def __str__(self):
+ if self.dfa.s0 is None:
+ return None
+ with StringIO() as buf:
+ for s in self.dfa.sortedStates():
+ n = 0
+ if s.edges is not None:
+ n = len(s.edges)
+ for i in range(0, n):
+ t = s.edges[i]
+ if t is not None and t.stateNumber != 0x7FFFFFFF:
+ buf.write(self.getStateString(s))
+ label = self.getEdgeLabel(i)
+ buf.write("-")
+ buf.write(label)
+ buf.write("->")
+ buf.write(self.getStateString(t))
+ buf.write('\n')
+ output = buf.getvalue()
+ if len(output)==0:
+ return None
+ else:
+ return output
+
+ def getEdgeLabel(self, i:int):
+ if i==0:
+ return "EOF"
+ if self.literalNames is not None and i<=len(self.literalNames):
+ return self.literalNames[i-1]
+ elif self.symbolicNames is not None and i<=len(self.symbolicNames):
+ return self.symbolicNames[i-1]
+ else:
+ return str(i-1)
+
+ def getStateString(self, s:DFAState):
+ n = s.stateNumber
+ baseStateStr = ( ":" if s.isAcceptState else "") + "s" + str(n) + ( "^" if s.requiresFullContext else "")
+ if s.isAcceptState:
+ if s.predicates is not None:
+ return baseStateStr + "=>" + str_list(s.predicates)
+ else:
+ return baseStateStr + "=>" + str(s.prediction)
+ else:
+ return baseStateStr
+
+class LexerDFASerializer(DFASerializer):
+
+ def __init__(self, dfa:DFA):
+ super().__init__(dfa, None)
+
+ def getEdgeLabel(self, i:int):
+ return "'" + chr(i) + "'"
diff --git a/cameras/deps/antlr4/dfa/DFAState.py b/cameras/deps/antlr4/dfa/DFAState.py
new file mode 100644
index 0000000000000000000000000000000000000000..51955a448886ea1fa34f0f4dff7fb0976edd1975
--- /dev/null
+++ b/cameras/deps/antlr4/dfa/DFAState.py
@@ -0,0 +1,126 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+# Map a predicate to a predicted alternative.#/
+from io import StringIO
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.atn.SemanticContext import SemanticContext
+
+
+class PredPrediction(object):
+ __slots__ = ('alt', 'pred')
+
+ def __init__(self, pred:SemanticContext, alt:int):
+ self.alt = alt
+ self.pred = pred
+
+ def __str__(self):
+ return "(" + str(self.pred) + ", " + str(self.alt) + ")"
+
+# A DFA state represents a set of possible ATN configurations.
+# As Aho, Sethi, Ullman p. 117 says "The DFA uses its state
+# to keep track of all possible states the ATN can be in after
+# reading each input symbol. That is to say, after reading
+# input a1a2..an, the DFA is in a state that represents the
+# subset T of the states of the ATN that are reachable from the
+# ATN's start state along some path labeled a1a2..an."
+# In conventional NFA→DFA conversion, therefore, the subset T
+# would be a bitset representing the set of states the
+# ATN could be in. We need to track the alt predicted by each
+# state as well, however. More importantly, we need to maintain
+# a stack of states, tracking the closure operations as they
+# jump from rule to rule, emulating rule invocations (method calls).
+# I have to add a stack to simulate the proper lookahead sequences for
+# the underlying LL grammar from which the ATN was derived.
+#
+#
I use a set of ATNConfig objects not simple states. An ATNConfig
+# is both a state (ala normal conversion) and a RuleContext describing
+# the chain of rules (if any) followed to arrive at that state.
+#
+#
A DFA state may have multiple references to a particular state,
+# but with different ATN contexts (with same or different alts)
+# meaning that state was reached via a different set of rule invocations.
+#/
+class DFAState(object):
+ __slots__ = (
+ 'stateNumber', 'configs', 'edges', 'isAcceptState', 'prediction',
+ 'lexerActionExecutor', 'requiresFullContext', 'predicates'
+ )
+
+ def __init__(self, stateNumber:int=-1, configs:ATNConfigSet=ATNConfigSet()):
+ self.stateNumber = stateNumber
+ self.configs = configs
+ # {@code edges[symbol]} points to target of symbol. Shift up by 1 so (-1)
+ # {@link Token#EOF} maps to {@code edges[0]}.
+ self.edges = None
+ self.isAcceptState = False
+ # if accept state, what ttype do we match or alt do we predict?
+ # This is set to {@link ATN#INVALID_ALT_NUMBER} when {@link #predicates}{@code !=null} or
+ # {@link #requiresFullContext}.
+ self.prediction = 0
+ self.lexerActionExecutor = None
+ # Indicates that this state was created during SLL prediction that
+ # discovered a conflict between the configurations in the state. Future
+ # {@link ParserATNSimulator#execATN} invocations immediately jumped doing
+ # full context prediction if this field is true.
+ self.requiresFullContext = False
+ # During SLL parsing, this is a list of predicates associated with the
+ # ATN configurations of the DFA state. When we have predicates,
+ # {@link #requiresFullContext} is {@code false} since full context prediction evaluates predicates
+ # on-the-fly. If this is not null, then {@link #prediction} is
+ # {@link ATN#INVALID_ALT_NUMBER}.
+ #
+ #
We only use these for non-{@link #requiresFullContext} but conflicting states. That
+ # means we know from the context (it's $ or we don't dip into outer
+ # context) that it's an ambiguity not a conflict.
+ #
+ #
This list is computed by {@link ParserATNSimulator#predicateDFAState}.
+ self.predicates = None
+
+
+
+ # Get the set of all alts mentioned by all ATN configurations in this
+ # DFA state.
+ def getAltSet(self):
+ if self.configs is not None:
+ return set(cfg.alt for cfg in self.configs) or None
+ return None
+
+ def __hash__(self):
+ return hash(self.configs)
+
+ # Two {@link DFAState} instances are equal if their ATN configuration sets
+ # are the same. This method is used to see if a state already exists.
+ #
+ #
Because the number of alternatives and number of ATN configurations are
+ # finite, there is a finite number of DFA states that can be processed.
+ # This is necessary to show that the algorithm terminates.
+ #
+ #
Cannot test the DFA state numbers here because in
+ # {@link ParserATNSimulator#addDFAState} we need to know if any other state
+ # exists that has this exact set of ATN configurations. The
+ # {@link #stateNumber} is irrelevant.
+ def __eq__(self, other):
+ # compare set of ATN configurations in this set with other
+ if self is other:
+ return True
+ elif not isinstance(other, DFAState):
+ return False
+ else:
+ return self.configs==other.configs
+
+ def __str__(self):
+ with StringIO() as buf:
+ buf.write(str(self.stateNumber))
+ buf.write(":")
+ buf.write(str(self.configs))
+ if self.isAcceptState:
+ buf.write("=>")
+ if self.predicates is not None:
+ buf.write(str(self.predicates))
+ else:
+ buf.write(str(self.prediction))
+ return buf.getvalue()
diff --git a/cameras/deps/antlr4/dfa/__init__.py b/cameras/deps/antlr4/dfa/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..216c000dc5ffc8e53cc9c596e420c1e67604d1aa
--- /dev/null
+++ b/cameras/deps/antlr4/dfa/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ericvergnaud'
diff --git a/cameras/deps/antlr4/error/DiagnosticErrorListener.py b/cameras/deps/antlr4/error/DiagnosticErrorListener.py
new file mode 100644
index 0000000000000000000000000000000000000000..32ac14b63579ce7c984c2e34f2b1c80bebe328ed
--- /dev/null
+++ b/cameras/deps/antlr4/error/DiagnosticErrorListener.py
@@ -0,0 +1,107 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+
+#
+# This implementation of {@link ANTLRErrorListener} can be used to identify
+# certain potential correctness and performance problems in grammars. "Reports"
+# are made by calling {@link Parser#notifyErrorListeners} with the appropriate
+# message.
+#
+#
+#
Ambiguities: These are cases where more than one path through the
+# grammar can match the input.
+#
Weak context sensitivity: These are cases where full-context
+# prediction resolved an SLL conflict to a unique alternative which equaled the
+# minimum alternative of the SLL conflict.
+#
Strong (forced) context sensitivity: These are cases where the
+# full-context prediction resolved an SLL conflict to a unique alternative,
+# and the minimum alternative of the SLL conflict was found to not be
+# a truly viable alternative. Two-stage parsing cannot be used for inputs where
+# this situation occurs.
+#
+
+from io import StringIO
+from antlr4 import Parser, DFA
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.error.ErrorListener import ErrorListener
+
+class DiagnosticErrorListener(ErrorListener):
+
+ def __init__(self, exactOnly:bool=True):
+ # whether all ambiguities or only exact ambiguities are reported.
+ self.exactOnly = exactOnly
+
+ def reportAmbiguity(self, recognizer:Parser, dfa:DFA, startIndex:int,
+ stopIndex:int, exact:bool, ambigAlts:set, configs:ATNConfigSet):
+ if self.exactOnly and not exact:
+ return
+
+ with StringIO() as buf:
+ buf.write("reportAmbiguity d=")
+ buf.write(self.getDecisionDescription(recognizer, dfa))
+ buf.write(": ambigAlts=")
+ buf.write(str(self.getConflictingAlts(ambigAlts, configs)))
+ buf.write(", input='")
+ buf.write(recognizer.getTokenStream().getText(startIndex, stopIndex))
+ buf.write("'")
+ recognizer.notifyErrorListeners(buf.getvalue())
+
+
+ def reportAttemptingFullContext(self, recognizer:Parser, dfa:DFA, startIndex:int,
+ stopIndex:int, conflictingAlts:set, configs:ATNConfigSet):
+ with StringIO() as buf:
+ buf.write("reportAttemptingFullContext d=")
+ buf.write(self.getDecisionDescription(recognizer, dfa))
+ buf.write(", input='")
+ buf.write(recognizer.getTokenStream().getText(startIndex, stopIndex))
+ buf.write("'")
+ recognizer.notifyErrorListeners(buf.getvalue())
+
+ def reportContextSensitivity(self, recognizer:Parser, dfa:DFA, startIndex:int,
+ stopIndex:int, prediction:int, configs:ATNConfigSet):
+ with StringIO() as buf:
+ buf.write("reportContextSensitivity d=")
+ buf.write(self.getDecisionDescription(recognizer, dfa))
+ buf.write(", input='")
+ buf.write(recognizer.getTokenStream().getText(startIndex, stopIndex))
+ buf.write("'")
+ recognizer.notifyErrorListeners(buf.getvalue())
+
+ def getDecisionDescription(self, recognizer:Parser, dfa:DFA):
+ decision = dfa.decision
+ ruleIndex = dfa.atnStartState.ruleIndex
+
+ ruleNames = recognizer.ruleNames
+ if ruleIndex < 0 or ruleIndex >= len(ruleNames):
+ return str(decision)
+
+ ruleName = ruleNames[ruleIndex]
+ if ruleName is None or len(ruleName)==0:
+ return str(decision)
+
+ return str(decision) + " (" + ruleName + ")"
+
+ #
+ # Computes the set of conflicting or ambiguous alternatives from a
+ # configuration set, if that information was not already provided by the
+ # parser.
+ #
+ # @param reportedAlts The set of conflicting or ambiguous alternatives, as
+ # reported by the parser.
+ # @param configs The conflicting or ambiguous configuration set.
+ # @return Returns {@code reportedAlts} if it is not {@code null}, otherwise
+ # returns the set of alternatives represented in {@code configs}.
+ #
+ def getConflictingAlts(self, reportedAlts:set, configs:ATNConfigSet):
+ if reportedAlts is not None:
+ return reportedAlts
+
+ result = set()
+ for config in configs:
+ result.add(config.alt)
+
+ return result
diff --git a/cameras/deps/antlr4/error/ErrorListener.py b/cameras/deps/antlr4/error/ErrorListener.py
new file mode 100644
index 0000000000000000000000000000000000000000..933264d431b9829f43a38d5f0f07c83bbad703a0
--- /dev/null
+++ b/cameras/deps/antlr4/error/ErrorListener.py
@@ -0,0 +1,72 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+
+# Provides an empty default implementation of {@link ANTLRErrorListener}. The
+# default implementation of each method does nothing, but can be overridden as
+# necessary.
+
+
+import sys
+
+class ErrorListener(object):
+
+ def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
+ pass
+
+ def reportAmbiguity(self, recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs):
+ pass
+
+ def reportAttemptingFullContext(self, recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs):
+ pass
+
+ def reportContextSensitivity(self, recognizer, dfa, startIndex, stopIndex, prediction, configs):
+ pass
+
+class ConsoleErrorListener(ErrorListener):
+ #
+ # Provides a default instance of {@link ConsoleErrorListener}.
+ #
+ INSTANCE = None
+
+ #
+ # {@inheritDoc}
+ #
+ #
+ # This implementation prints messages to {@link System#err} containing the
+ # values of {@code line}, {@code charPositionInLine}, and {@code msg} using
+ # the following format.
+ #
+ #
+ # line line:charPositionInLinemsg
+ #
+ #
+ def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
+ print("line " + str(line) + ":" + str(column) + " " + msg, file=sys.stderr)
+
+ConsoleErrorListener.INSTANCE = ConsoleErrorListener()
+
+class ProxyErrorListener(ErrorListener):
+
+ def __init__(self, delegates):
+ super().__init__()
+ if delegates is None:
+ raise ReferenceError("delegates")
+ self.delegates = delegates
+
+ def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
+ for delegate in self.delegates:
+ delegate.syntaxError(recognizer, offendingSymbol, line, column, msg, e)
+
+ def reportAmbiguity(self, recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs):
+ for delegate in self.delegates:
+ delegate.reportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs)
+
+ def reportAttemptingFullContext(self, recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs):
+ for delegate in self.delegates:
+ delegate.reportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs)
+
+ def reportContextSensitivity(self, recognizer, dfa, startIndex, stopIndex, prediction, configs):
+ for delegate in self.delegates:
+ delegate.reportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs)
diff --git a/cameras/deps/antlr4/error/ErrorStrategy.py b/cameras/deps/antlr4/error/ErrorStrategy.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f7caadb240445e6d997ad582a51836f95cab5c5
--- /dev/null
+++ b/cameras/deps/antlr4/error/ErrorStrategy.py
@@ -0,0 +1,709 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+import sys
+from antlr4.IntervalSet import IntervalSet
+
+from antlr4.Token import Token
+from antlr4.atn.ATNState import ATNState
+from antlr4.error.Errors import RecognitionException, NoViableAltException, InputMismatchException, \
+ FailedPredicateException, ParseCancellationException
+
+# need forward declaration
+Parser = None
+
+class ErrorStrategy(object):
+
+ def reset(self, recognizer:Parser):
+ pass
+
+ def recoverInline(self, recognizer:Parser):
+ pass
+
+ def recover(self, recognizer:Parser, e:RecognitionException):
+ pass
+
+ def sync(self, recognizer:Parser):
+ pass
+
+ def inErrorRecoveryMode(self, recognizer:Parser):
+ pass
+
+ def reportError(self, recognizer:Parser, e:RecognitionException):
+ pass
+
+
+# This is the default implementation of {@link ANTLRErrorStrategy} used for
+# error reporting and recovery in ANTLR parsers.
+#
+class DefaultErrorStrategy(ErrorStrategy):
+
+ def __init__(self):
+ super().__init__()
+ # Indicates whether the error strategy is currently "recovering from an
+ # error". This is used to suppress reporting multiple error messages while
+ # attempting to recover from a detected syntax error.
+ #
+ # @see #inErrorRecoveryMode
+ #
+ self.errorRecoveryMode = False
+
+ # The index into the input stream where the last error occurred.
+ # This is used to prevent infinite loops where an error is found
+ # but no token is consumed during recovery...another error is found,
+ # ad nauseum. This is a failsafe mechanism to guarantee that at least
+ # one token/tree node is consumed for two errors.
+ #
+ self.lastErrorIndex = -1
+ self.lastErrorStates = None
+ self.nextTokensContext = None
+ self.nextTokenState = 0
+
+ #
The default implementation simply calls {@link #endErrorCondition} to
+ # ensure that the handler is not in error recovery mode.
+ def reset(self, recognizer:Parser):
+ self.endErrorCondition(recognizer)
+
+ #
+ # This method is called to enter error recovery mode when a recognition
+ # exception is reported.
+ #
+ # @param recognizer the parser instance
+ #
+ def beginErrorCondition(self, recognizer:Parser):
+ self.errorRecoveryMode = True
+
+ def inErrorRecoveryMode(self, recognizer:Parser):
+ return self.errorRecoveryMode
+
+ #
+ # This method is called to leave error recovery mode after recovering from
+ # a recognition exception.
+ #
+ # @param recognizer
+ #
+ def endErrorCondition(self, recognizer:Parser):
+ self.errorRecoveryMode = False
+ self.lastErrorStates = None
+ self.lastErrorIndex = -1
+
+ #
+ # {@inheritDoc}
+ #
+ #
The default implementation simply calls {@link #endErrorCondition}.
The default implementation returns immediately if the handler is already
+ # in error recovery mode. Otherwise, it calls {@link #beginErrorCondition}
+ # and dispatches the reporting task based on the runtime type of {@code e}
+ # according to the following table.
+ #
+ #
+ #
{@link NoViableAltException}: Dispatches the call to
+ # {@link #reportNoViableAlternative}
+ #
{@link InputMismatchException}: Dispatches the call to
+ # {@link #reportInputMismatch}
+ #
{@link FailedPredicateException}: Dispatches the call to
+ # {@link #reportFailedPredicate}
+ #
All other types: calls {@link Parser#notifyErrorListeners} to report
+ # the exception
+ #
+ #
+ def reportError(self, recognizer:Parser, e:RecognitionException):
+ # if we've already reported an error and have not matched a token
+ # yet successfully, don't report any errors.
+ if self.inErrorRecoveryMode(recognizer):
+ return # don't report spurious errors
+ self.beginErrorCondition(recognizer)
+ if isinstance( e, NoViableAltException ):
+ self.reportNoViableAlternative(recognizer, e)
+ elif isinstance( e, InputMismatchException ):
+ self.reportInputMismatch(recognizer, e)
+ elif isinstance( e, FailedPredicateException ):
+ self.reportFailedPredicate(recognizer, e)
+ else:
+ print("unknown recognition error type: " + type(e).__name__)
+ recognizer.notifyErrorListeners(e.message, e.offendingToken, e)
+
+ #
+ # {@inheritDoc}
+ #
+ #
The default implementation resynchronizes the parser by consuming tokens
+ # until we find one in the resynchronization set--loosely the set of tokens
+ # that can follow the current rule.
+ #
+ def recover(self, recognizer:Parser, e:RecognitionException):
+ if self.lastErrorIndex==recognizer.getInputStream().index \
+ and self.lastErrorStates is not None \
+ and recognizer.state in self.lastErrorStates:
+ # uh oh, another error at same token index and previously-visited
+ # state in ATN; must be a case where LT(1) is in the recovery
+ # token set so nothing got consumed. Consume a single token
+ # at least to prevent an infinite loop; this is a failsafe.
+ recognizer.consume()
+
+ self.lastErrorIndex = recognizer._input.index
+ if self.lastErrorStates is None:
+ self.lastErrorStates = []
+ self.lastErrorStates.append(recognizer.state)
+ followSet = self.getErrorRecoverySet(recognizer)
+ self.consumeUntil(recognizer, followSet)
+
+ # The default implementation of {@link ANTLRErrorStrategy#sync} makes sure
+ # that the current lookahead symbol is consistent with what were expecting
+ # at this point in the ATN. You can call this anytime but ANTLR only
+ # generates code to check before subrules/loops and each iteration.
+ #
+ #
Implements Jim Idle's magic sync mechanism in closures and optional
+ # subrules. E.g.,
+ #
+ #
+ # a : sync ( stuff sync )* ;
+ # sync : {consume to what can follow sync} ;
+ #
+ #
+ # At the start of a sub rule upon error, {@link #sync} performs single
+ # token deletion, if possible. If it can't do that, it bails on the current
+ # rule and uses the default error recovery, which consumes until the
+ # resynchronization set of the current rule.
+ #
+ #
If the sub rule is optional ({@code (...)?}, {@code (...)*}, or block
+ # with an empty alternative), then the expected set includes what follows
+ # the subrule.
+ #
+ #
During loop iteration, it consumes until it sees a token that can start a
+ # sub rule or what follows loop. Yes, that is pretty aggressive. We opt to
+ # stay in the loop as long as possible.
+ #
+ #
ORIGINS
+ #
+ #
Previous versions of ANTLR did a poor job of their recovery within loops.
+ # A single mismatch token or missing token would force the parser to bail
+ # out of the entire rules surrounding the loop. So, for rule
+ #
+ #
+ # classDef : 'class' ID '{' member* '}'
+ #
+ #
+ # input with an extra token between members would force the parser to
+ # consume until it found the next class definition rather than the next
+ # member definition of the current class.
+ #
+ #
This functionality cost a little bit of effort because the parser has to
+ # compare token set at the start of the loop and at each iteration. If for
+ # some reason speed is suffering for you, you can turn off this
+ # functionality by simply overriding this method as a blank { }.
+ #
+ def sync(self, recognizer:Parser):
+ # If already recovering, don't try to sync
+ if self.inErrorRecoveryMode(recognizer):
+ return
+
+ s = recognizer._interp.atn.states[recognizer.state]
+ la = recognizer.getTokenStream().LA(1)
+ # try cheaper subset first; might get lucky. seems to shave a wee bit off
+ nextTokens = recognizer.atn.nextTokens(s)
+ if la in nextTokens:
+ self.nextTokensContext = None
+ self.nextTokenState = ATNState.INVALID_STATE_NUMBER
+ return
+ elif Token.EPSILON in nextTokens:
+ if self.nextTokensContext is None:
+ # It's possible the next token won't match information tracked
+ # by sync is restricted for performance.
+ self.nextTokensContext = recognizer._ctx
+ self.nextTokensState = recognizer._stateNumber
+ return
+
+ if s.stateType in [ATNState.BLOCK_START, ATNState.STAR_BLOCK_START,
+ ATNState.PLUS_BLOCK_START, ATNState.STAR_LOOP_ENTRY]:
+ # report error and recover if possible
+ if self.singleTokenDeletion(recognizer)is not None:
+ return
+ else:
+ raise InputMismatchException(recognizer)
+
+ elif s.stateType in [ATNState.PLUS_LOOP_BACK, ATNState.STAR_LOOP_BACK]:
+ self.reportUnwantedToken(recognizer)
+ expecting = recognizer.getExpectedTokens()
+ whatFollowsLoopIterationOrRule = expecting.addSet(self.getErrorRecoverySet(recognizer))
+ self.consumeUntil(recognizer, whatFollowsLoopIterationOrRule)
+
+ else:
+ # do nothing if we can't identify the exact kind of ATN state
+ pass
+
+ # This is called by {@link #reportError} when the exception is a
+ # {@link NoViableAltException}.
+ #
+ # @see #reportError
+ #
+ # @param recognizer the parser instance
+ # @param e the recognition exception
+ #
+ def reportNoViableAlternative(self, recognizer:Parser, e:NoViableAltException):
+ tokens = recognizer.getTokenStream()
+ if tokens is not None:
+ if e.startToken.type==Token.EOF:
+ input = ""
+ else:
+ input = tokens.getText(e.startToken, e.offendingToken)
+ else:
+ input = ""
+ msg = "no viable alternative at input " + self.escapeWSAndQuote(input)
+ recognizer.notifyErrorListeners(msg, e.offendingToken, e)
+
+ #
+ # This is called by {@link #reportError} when the exception is an
+ # {@link InputMismatchException}.
+ #
+ # @see #reportError
+ #
+ # @param recognizer the parser instance
+ # @param e the recognition exception
+ #
+ def reportInputMismatch(self, recognizer:Parser, e:InputMismatchException):
+ msg = "mismatched input " + self.getTokenErrorDisplay(e.offendingToken) \
+ + " expecting " + e.getExpectedTokens().toString(recognizer.literalNames, recognizer.symbolicNames)
+ recognizer.notifyErrorListeners(msg, e.offendingToken, e)
+
+ #
+ # This is called by {@link #reportError} when the exception is a
+ # {@link FailedPredicateException}.
+ #
+ # @see #reportError
+ #
+ # @param recognizer the parser instance
+ # @param e the recognition exception
+ #
+ def reportFailedPredicate(self, recognizer, e):
+ ruleName = recognizer.ruleNames[recognizer._ctx.getRuleIndex()]
+ msg = "rule " + ruleName + " " + e.message
+ recognizer.notifyErrorListeners(msg, e.offendingToken, e)
+
+ # This method is called to report a syntax error which requires the removal
+ # of a token from the input stream. At the time this method is called, the
+ # erroneous symbol is current {@code LT(1)} symbol and has not yet been
+ # removed from the input stream. When this method returns,
+ # {@code recognizer} is in error recovery mode.
+ #
+ #
This method is called when {@link #singleTokenDeletion} identifies
+ # single-token deletion as a viable recovery strategy for a mismatched
+ # input error.
+ #
+ #
The default implementation simply returns if the handler is already in
+ # error recovery mode. Otherwise, it calls {@link #beginErrorCondition} to
+ # enter error recovery mode, followed by calling
+ # {@link Parser#notifyErrorListeners}.
+ #
+ # @param recognizer the parser instance
+ #
+ def reportUnwantedToken(self, recognizer:Parser):
+ if self.inErrorRecoveryMode(recognizer):
+ return
+
+ self.beginErrorCondition(recognizer)
+ t = recognizer.getCurrentToken()
+ tokenName = self.getTokenErrorDisplay(t)
+ expecting = self.getExpectedTokens(recognizer)
+ msg = "extraneous input " + tokenName + " expecting " \
+ + expecting.toString(recognizer.literalNames, recognizer.symbolicNames)
+ recognizer.notifyErrorListeners(msg, t, None)
+
+ # This method is called to report a syntax error which requires the
+ # insertion of a missing token into the input stream. At the time this
+ # method is called, the missing token has not yet been inserted. When this
+ # method returns, {@code recognizer} is in error recovery mode.
+ #
+ #
This method is called when {@link #singleTokenInsertion} identifies
+ # single-token insertion as a viable recovery strategy for a mismatched
+ # input error.
+ #
+ #
The default implementation simply returns if the handler is already in
+ # error recovery mode. Otherwise, it calls {@link #beginErrorCondition} to
+ # enter error recovery mode, followed by calling
+ # {@link Parser#notifyErrorListeners}.
The default implementation attempts to recover from the mismatched input
+ # by using single token insertion and deletion as described below. If the
+ # recovery attempt fails, this method throws an
+ # {@link InputMismatchException}.
+ #
+ #
EXTRA TOKEN (single token deletion)
+ #
+ #
{@code LA(1)} is not what we are looking for. If {@code LA(2)} has the
+ # right token, however, then assume {@code LA(1)} is some extra spurious
+ # token and delete it. Then consume and return the next token (which was
+ # the {@code LA(2)} token) as the successful result of the match operation.
+ #
+ #
This recovery strategy is implemented by {@link #singleTokenDeletion}.
+ #
+ #
MISSING TOKEN (single token insertion)
+ #
+ #
If current token (at {@code LA(1)}) is consistent with what could come
+ # after the expected {@code LA(1)} token, then assume the token is missing
+ # and use the parser's {@link TokenFactory} to create it on the fly. The
+ # "insertion" is performed by returning the created token as the successful
+ # result of the match operation.
+ #
+ #
This recovery strategy is implemented by {@link #singleTokenInsertion}.
+ #
+ #
EXAMPLE
+ #
+ #
For example, Input {@code i=(3;} is clearly missing the {@code ')'}. When
+ # the parser returns from the nested call to {@code expr}, it will have
+ # call chain:
+ #
+ #
+ # stat → expr → atom
+ #
+ #
+ # and it will be trying to match the {@code ')'} at this point in the
+ # derivation:
+ #
+ #
+ # => ID '=' '(' INT ')' ('+' atom)* ';'
+ # ^
+ #
+ #
+ # The attempt to match {@code ')'} will fail when it sees {@code ';'} and
+ # call {@link #recoverInline}. To recover, it sees that {@code LA(1)==';'}
+ # is in the set of tokens that can follow the {@code ')'} token reference
+ # in rule {@code atom}. It can assume that you forgot the {@code ')'}.
+ #
+ def recoverInline(self, recognizer:Parser):
+ # SINGLE TOKEN DELETION
+ matchedSymbol = self.singleTokenDeletion(recognizer)
+ if matchedSymbol is not None:
+ # we have deleted the extra token.
+ # now, move past ttype token as if all were ok
+ recognizer.consume()
+ return matchedSymbol
+
+ # SINGLE TOKEN INSERTION
+ if self.singleTokenInsertion(recognizer):
+ return self.getMissingSymbol(recognizer)
+
+ # even that didn't work; must throw the exception
+ raise InputMismatchException(recognizer)
+
+ #
+ # This method implements the single-token insertion inline error recovery
+ # strategy. It is called by {@link #recoverInline} if the single-token
+ # deletion strategy fails to recover from the mismatched input. If this
+ # method returns {@code true}, {@code recognizer} will be in error recovery
+ # mode.
+ #
+ #
This method determines whether or not single-token insertion is viable by
+ # checking if the {@code LA(1)} input symbol could be successfully matched
+ # if it were instead the {@code LA(2)} symbol. If this method returns
+ # {@code true}, the caller is responsible for creating and inserting a
+ # token with the correct type to produce this behavior.
+ #
+ # @param recognizer the parser instance
+ # @return {@code true} if single-token insertion is a viable recovery
+ # strategy for the current mismatched input, otherwise {@code false}
+ #
+ def singleTokenInsertion(self, recognizer:Parser):
+ currentSymbolType = recognizer.getTokenStream().LA(1)
+ # if current token is consistent with what could come after current
+ # ATN state, then we know we're missing a token; error recovery
+ # is free to conjure up and insert the missing token
+ atn = recognizer._interp.atn
+ currentState = atn.states[recognizer.state]
+ next = currentState.transitions[0].target
+ expectingAtLL2 = atn.nextTokens(next, recognizer._ctx)
+ if currentSymbolType in expectingAtLL2:
+ self.reportMissingToken(recognizer)
+ return True
+ else:
+ return False
+
+ # This method implements the single-token deletion inline error recovery
+ # strategy. It is called by {@link #recoverInline} to attempt to recover
+ # from mismatched input. If this method returns null, the parser and error
+ # handler state will not have changed. If this method returns non-null,
+ # {@code recognizer} will not be in error recovery mode since the
+ # returned token was a successful match.
+ #
+ #
If the single-token deletion is successful, this method calls
+ # {@link #reportUnwantedToken} to report the error, followed by
+ # {@link Parser#consume} to actually "delete" the extraneous token. Then,
+ # before returning {@link #reportMatch} is called to signal a successful
+ # match.
+ #
+ # @param recognizer the parser instance
+ # @return the successfully matched {@link Token} instance if single-token
+ # deletion successfully recovers from the mismatched input, otherwise
+ # {@code null}
+ #
+ def singleTokenDeletion(self, recognizer:Parser):
+ nextTokenType = recognizer.getTokenStream().LA(2)
+ expecting = self.getExpectedTokens(recognizer)
+ if nextTokenType in expecting:
+ self.reportUnwantedToken(recognizer)
+ # print("recoverFromMismatchedToken deleting " \
+ # + str(recognizer.getTokenStream().LT(1)) \
+ # + " since " + str(recognizer.getTokenStream().LT(2)) \
+ # + " is what we want", file=sys.stderr)
+ recognizer.consume() # simply delete extra token
+ # we want to return the token we're actually matching
+ matchedSymbol = recognizer.getCurrentToken()
+ self.reportMatch(recognizer) # we know current token is correct
+ return matchedSymbol
+ else:
+ return None
+
+ # Conjure up a missing token during error recovery.
+ #
+ # The recognizer attempts to recover from single missing
+ # symbols. But, actions might refer to that missing symbol.
+ # For example, x=ID {f($x);}. The action clearly assumes
+ # that there has been an identifier matched previously and that
+ # $x points at that token. If that token is missing, but
+ # the next token in the stream is what we want we assume that
+ # this token is missing and we keep going. Because we
+ # have to return some token to replace the missing token,
+ # we have to conjure one up. This method gives the user control
+ # over the tokens returned for missing tokens. Mostly,
+ # you will want to create something special for identifier
+ # tokens. For literals such as '{' and ',', the default
+ # action in the parser or tree parser works. It simply creates
+ # a CommonToken of the appropriate type. The text will be the token.
+ # If you change what tokens must be created by the lexer,
+ # override this method to create the appropriate tokens.
+ #
+ def getMissingSymbol(self, recognizer:Parser):
+ currentSymbol = recognizer.getCurrentToken()
+ expecting = self.getExpectedTokens(recognizer)
+ expectedTokenType = expecting[0] # get any element
+ if expectedTokenType==Token.EOF:
+ tokenText = ""
+ else:
+ name = None
+ if expectedTokenType < len(recognizer.literalNames):
+ name = recognizer.literalNames[expectedTokenType]
+ if name is None and expectedTokenType < len(recognizer.symbolicNames):
+ name = recognizer.symbolicNames[expectedTokenType]
+ tokenText = ""
+ current = currentSymbol
+ lookback = recognizer.getTokenStream().LT(-1)
+ if current.type==Token.EOF and lookback is not None:
+ current = lookback
+ return recognizer.getTokenFactory().create(current.source,
+ expectedTokenType, tokenText, Token.DEFAULT_CHANNEL,
+ -1, -1, current.line, current.column)
+
+ def getExpectedTokens(self, recognizer:Parser):
+ return recognizer.getExpectedTokens()
+
+ # How should a token be displayed in an error message? The default
+ # is to display just the text, but during development you might
+ # want to have a lot of information spit out. Override in that case
+ # to use t.toString() (which, for CommonToken, dumps everything about
+ # the token). This is better than forcing you to override a method in
+ # your token objects because you don't have to go modify your lexer
+ # so that it creates a new Java type.
+ #
+ def getTokenErrorDisplay(self, t:Token):
+ if t is None:
+ return ""
+ s = t.text
+ if s is None:
+ if t.type==Token.EOF:
+ s = ""
+ else:
+ s = "<" + str(t.type) + ">"
+ return self.escapeWSAndQuote(s)
+
+ def escapeWSAndQuote(self, s:str):
+ s = s.replace("\n","\\n")
+ s = s.replace("\r","\\r")
+ s = s.replace("\t","\\t")
+ return "'" + s + "'"
+
+ # Compute the error recovery set for the current rule. During
+ # rule invocation, the parser pushes the set of tokens that can
+ # follow that rule reference on the stack; this amounts to
+ # computing FIRST of what follows the rule reference in the
+ # enclosing rule. See LinearApproximator.FIRST().
+ # This local follow set only includes tokens
+ # from within the rule; i.e., the FIRST computation done by
+ # ANTLR stops at the end of a rule.
+ #
+ # EXAMPLE
+ #
+ # When you find a "no viable alt exception", the input is not
+ # consistent with any of the alternatives for rule r. The best
+ # thing to do is to consume tokens until you see something that
+ # can legally follow a call to r#or* any rule that called r.
+ # You don't want the exact set of viable next tokens because the
+ # input might just be missing a token--you might consume the
+ # rest of the input looking for one of the missing tokens.
+ #
+ # Consider grammar:
+ #
+ # a : '[' b ']'
+ # | '(' b ')'
+ # ;
+ # b : c '^' INT ;
+ # c : ID
+ # | INT
+ # ;
+ #
+ # At each rule invocation, the set of tokens that could follow
+ # that rule is pushed on a stack. Here are the various
+ # context-sensitive follow sets:
+ #
+ # FOLLOW(b1_in_a) = FIRST(']') = ']'
+ # FOLLOW(b2_in_a) = FIRST(')') = ')'
+ # FOLLOW(c_in_b) = FIRST('^') = '^'
+ #
+ # Upon erroneous input "[]", the call chain is
+ #
+ # a -> b -> c
+ #
+ # and, hence, the follow context stack is:
+ #
+ # depth follow set start of rule execution
+ # 0 a (from main())
+ # 1 ']' b
+ # 2 '^' c
+ #
+ # Notice that ')' is not included, because b would have to have
+ # been called from a different context in rule a for ')' to be
+ # included.
+ #
+ # For error recovery, we cannot consider FOLLOW(c)
+ # (context-sensitive or otherwise). We need the combined set of
+ # all context-sensitive FOLLOW sets--the set of all tokens that
+ # could follow any reference in the call chain. We need to
+ # resync to one of those tokens. Note that FOLLOW(c)='^' and if
+ # we resync'd to that token, we'd consume until EOF. We need to
+ # sync to context-sensitive FOLLOWs for a, b, and c: {']','^'}.
+ # In this case, for input "[]", LA(1) is ']' and in the set, so we would
+ # not consume anything. After printing an error, rule c would
+ # return normally. Rule b would not find the required '^' though.
+ # At this point, it gets a mismatched token error and throws an
+ # exception (since LA(1) is not in the viable following token
+ # set). The rule exception handler tries to recover, but finds
+ # the same recovery set and doesn't consume anything. Rule b
+ # exits normally returning to rule a. Now it finds the ']' (and
+ # with the successful match exits errorRecovery mode).
+ #
+ # So, you can see that the parser walks up the call chain looking
+ # for the token that was a member of the recovery set.
+ #
+ # Errors are not generated in errorRecovery mode.
+ #
+ # ANTLR's error recovery mechanism is based upon original ideas:
+ #
+ # "Algorithms + Data Structures = Programs" by Niklaus Wirth
+ #
+ # and
+ #
+ # "A note on error recovery in recursive descent parsers":
+ # http:#portal.acm.org/citation.cfm?id=947902.947905
+ #
+ # Later, Josef Grosch had some good ideas:
+ #
+ # "Efficient and Comfortable Error Recovery in Recursive Descent
+ # Parsers":
+ # ftp:#www.cocolab.com/products/cocktail/doca4.ps/ell.ps.zip
+ #
+ # Like Grosch I implement context-sensitive FOLLOW sets that are combined
+ # at run-time upon error to avoid overhead during parsing.
+ #
+ def getErrorRecoverySet(self, recognizer:Parser):
+ atn = recognizer._interp.atn
+ ctx = recognizer._ctx
+ recoverSet = IntervalSet()
+ while ctx is not None and ctx.invokingState>=0:
+ # compute what follows who invoked us
+ invokingState = atn.states[ctx.invokingState]
+ rt = invokingState.transitions[0]
+ follow = atn.nextTokens(rt.followState)
+ recoverSet.addSet(follow)
+ ctx = ctx.parentCtx
+ recoverSet.removeOne(Token.EPSILON)
+ return recoverSet
+
+ # Consume tokens until one matches the given token set.#
+ def consumeUntil(self, recognizer:Parser, set_:set):
+ ttype = recognizer.getTokenStream().LA(1)
+ while ttype != Token.EOF and not ttype in set_:
+ recognizer.consume()
+ ttype = recognizer.getTokenStream().LA(1)
+
+
+#
+# This implementation of {@link ANTLRErrorStrategy} responds to syntax errors
+# by immediately canceling the parse operation with a
+# {@link ParseCancellationException}. The implementation ensures that the
+# {@link ParserRuleContext#exception} field is set for all parse tree nodes
+# that were not completed prior to encountering the error.
+#
+#
+# This error strategy is useful in the following scenarios.
+#
+#
+#
Two-stage parsing: This error strategy allows the first
+# stage of two-stage parsing to immediately terminate if an error is
+# encountered, and immediately fall back to the second stage. In addition to
+# avoiding wasted work by attempting to recover from errors here, the empty
+# implementation of {@link BailErrorStrategy#sync} improves the performance of
+# the first stage.
+#
Silent validation: When syntax errors are not being
+# reported or logged, and the parse result is simply ignored if errors occur,
+# the {@link BailErrorStrategy} avoids wasting work on recovering from errors
+# when the result will be ignored either way.
+#
+# @see Parser#setErrorHandler(ANTLRErrorStrategy)
+#
+class BailErrorStrategy(DefaultErrorStrategy):
+ # Instead of recovering from exception {@code e}, re-throw it wrapped
+ # in a {@link ParseCancellationException} so it is not caught by the
+ # rule function catches. Use {@link Exception#getCause()} to get the
+ # original {@link RecognitionException}.
+ #
+ def recover(self, recognizer:Parser, e:RecognitionException):
+ context = recognizer._ctx
+ while context is not None:
+ context.exception = e
+ context = context.parentCtx
+ raise ParseCancellationException(e)
+
+ # Make sure we don't attempt to recover inline; if the parser
+ # successfully recovers, it won't throw an exception.
+ #
+ def recoverInline(self, recognizer:Parser):
+ self.recover(recognizer, InputMismatchException(recognizer))
+
+ # Make sure we don't attempt to recover from problems in subrules.#
+ def sync(self, recognizer:Parser):
+ pass
+
+del Parser
\ No newline at end of file
diff --git a/cameras/deps/antlr4/error/Errors.py b/cameras/deps/antlr4/error/Errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..e78ac05911d3c9569441fe376ff7d6c686c05c95
--- /dev/null
+++ b/cameras/deps/antlr4/error/Errors.py
@@ -0,0 +1,172 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+# need forward declaration
+Token = None
+Lexer = None
+Parser = None
+TokenStream = None
+ATNConfigSet = None
+ParserRulecontext = None
+PredicateTransition = None
+BufferedTokenStream = None
+
+class UnsupportedOperationException(Exception):
+
+ def __init__(self, msg:str):
+ super().__init__(msg)
+
+class IllegalStateException(Exception):
+
+ def __init__(self, msg:str):
+ super().__init__(msg)
+
+class CancellationException(IllegalStateException):
+
+ def __init__(self, msg:str):
+ super().__init__(msg)
+
+# The root of the ANTLR exception hierarchy. In general, ANTLR tracks just
+# 3 kinds of errors: prediction errors, failed predicate errors, and
+# mismatched input errors. In each case, the parser knows where it is
+# in the input, where it is in the ATN, the rule invocation stack,
+# and what kind of problem occurred.
+
+from antlr4.InputStream import InputStream
+from antlr4.ParserRuleContext import ParserRuleContext
+from antlr4.Recognizer import Recognizer
+
+class RecognitionException(Exception):
+
+
+ def __init__(self, message:str=None, recognizer:Recognizer=None, input:InputStream=None, ctx:ParserRulecontext=None):
+ super().__init__(message)
+ self.message = message
+ self.recognizer = recognizer
+ self.input = input
+ self.ctx = ctx
+ # The current {@link Token} when an error occurred. Since not all streams
+ # support accessing symbols by index, we have to track the {@link Token}
+ # instance itself.
+ self.offendingToken = None
+ # Get the ATN state number the parser was in at the time the error
+ # occurred. For {@link NoViableAltException} and
+ # {@link LexerNoViableAltException} exceptions, this is the
+ # {@link DecisionState} number. For others, it is the state whose outgoing
+ # edge we couldn't match.
+ self.offendingState = -1
+ if recognizer is not None:
+ self.offendingState = recognizer.state
+
+ #
If the state number is not known, this method returns -1.
+
+ #
+ # Gets the set of input symbols which could potentially follow the
+ # previously matched symbol at the time this exception was thrown.
+ #
+ #
If the set of expected tokens is not known and could not be computed,
+ # this method returns {@code null}.
+ #
+ # @return The set of token types that could potentially follow the current
+ # state in the ATN, or {@code null} if the information is not available.
+ #/
+ def getExpectedTokens(self):
+ if self.recognizer is not None:
+ return self.recognizer.atn.getExpectedTokens(self.offendingState, self.ctx)
+ else:
+ return None
+
+
+class LexerNoViableAltException(RecognitionException):
+
+ def __init__(self, lexer:Lexer, input:InputStream, startIndex:int, deadEndConfigs:ATNConfigSet):
+ super().__init__(message=None, recognizer=lexer, input=input, ctx=None)
+ self.startIndex = startIndex
+ self.deadEndConfigs = deadEndConfigs
+
+ def __str__(self):
+ symbol = ""
+ if self.startIndex >= 0 and self.startIndex < self.input.size:
+ symbol = self.input.getText(self.startIndex, self.startIndex)
+ # TODO symbol = Utils.escapeWhitespace(symbol, false);
+ return "LexerNoViableAltException('" + symbol + "')"
+
+# Indicates that the parser could not decide which of two or more paths
+# to take based upon the remaining input. It tracks the starting token
+# of the offending input and also knows where the parser was
+# in the various paths when the error. Reported by reportNoViableAlternative()
+#
+class NoViableAltException(RecognitionException):
+
+ def __init__(self, recognizer:Parser, input:TokenStream=None, startToken:Token=None,
+ offendingToken:Token=None, deadEndConfigs:ATNConfigSet=None, ctx:ParserRuleContext=None):
+ if ctx is None:
+ ctx = recognizer._ctx
+ if offendingToken is None:
+ offendingToken = recognizer.getCurrentToken()
+ if startToken is None:
+ startToken = recognizer.getCurrentToken()
+ if input is None:
+ input = recognizer.getInputStream()
+ super().__init__(recognizer=recognizer, input=input, ctx=ctx)
+ # Which configurations did we try at input.index() that couldn't match input.LT(1)?#
+ self.deadEndConfigs = deadEndConfigs
+ # The token object at the start index; the input stream might
+ # not be buffering tokens so get a reference to it. (At the
+ # time the error occurred, of course the stream needs to keep a
+ # buffer all of the tokens but later we might not have access to those.)
+ self.startToken = startToken
+ self.offendingToken = offendingToken
+
+# This signifies any kind of mismatched input exceptions such as
+# when the current input does not match the expected token.
+#
+class InputMismatchException(RecognitionException):
+
+ def __init__(self, recognizer:Parser):
+ super().__init__(recognizer=recognizer, input=recognizer.getInputStream(), ctx=recognizer._ctx)
+ self.offendingToken = recognizer.getCurrentToken()
+
+
+# A semantic predicate failed during validation. Validation of predicates
+# occurs when normally parsing the alternative just like matching a token.
+# Disambiguating predicate evaluation occurs when we test a predicate during
+# prediction.
+
+class FailedPredicateException(RecognitionException):
+
+ def __init__(self, recognizer:Parser, predicate:str=None, message:str=None):
+ super().__init__(message=self.formatMessage(predicate,message), recognizer=recognizer,
+ input=recognizer.getInputStream(), ctx=recognizer._ctx)
+ s = recognizer._interp.atn.states[recognizer.state]
+ trans = s.transitions[0]
+ from antlr4.atn.Transition import PredicateTransition
+ if isinstance(trans, PredicateTransition):
+ self.ruleIndex = trans.ruleIndex
+ self.predicateIndex = trans.predIndex
+ else:
+ self.ruleIndex = 0
+ self.predicateIndex = 0
+ self.predicate = predicate
+ self.offendingToken = recognizer.getCurrentToken()
+
+ def formatMessage(self, predicate:str, message:str):
+ if message is not None:
+ return message
+ else:
+ return "failed predicate: {" + predicate + "}?"
+
+class ParseCancellationException(CancellationException):
+
+ pass
+
+del Token
+del Lexer
+del Parser
+del TokenStream
+del ATNConfigSet
+del ParserRulecontext
+del PredicateTransition
+del BufferedTokenStream
diff --git a/cameras/deps/antlr4/error/__init__.py b/cameras/deps/antlr4/error/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..216c000dc5ffc8e53cc9c596e420c1e67604d1aa
--- /dev/null
+++ b/cameras/deps/antlr4/error/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ericvergnaud'
diff --git a/cameras/deps/antlr4/tree/Chunk.py b/cameras/deps/antlr4/tree/Chunk.py
new file mode 100644
index 0000000000000000000000000000000000000000..081419a34f65463b370b848b141192bfe491befd
--- /dev/null
+++ b/cameras/deps/antlr4/tree/Chunk.py
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+class Chunk(object):
+ pass
+
+class TagChunk(Chunk):
+ __slots__ = ('tag', 'label')
+
+ def __init__(self, tag:str, label:str=None):
+ self.tag = tag
+ self.label = label
+
+ def __str__(self):
+ if self.label is None:
+ return self.tag
+ else:
+ return self.label + ":" + self.tag
+
+class TextChunk(Chunk):
+ __slots__ = 'text'
+
+ def __init__(self, text:str):
+ self.text = text
+
+ def __str__(self):
+ return "'" + self.text + "'"
diff --git a/cameras/deps/antlr4/tree/ParseTreeMatch.py b/cameras/deps/antlr4/tree/ParseTreeMatch.py
new file mode 100644
index 0000000000000000000000000000000000000000..c02bc0357d26b343a72307cda77ff62fe307a44a
--- /dev/null
+++ b/cameras/deps/antlr4/tree/ParseTreeMatch.py
@@ -0,0 +1,118 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+
+#
+# Represents the result of matching a {@link ParseTree} against a tree pattern.
+#
+from io import StringIO
+from antlr4.tree.ParseTreePattern import ParseTreePattern
+from antlr4.tree.Tree import ParseTree
+
+
+class ParseTreeMatch(object):
+ __slots__ = ('tree', 'pattern', 'labels', 'mismatchedNode')
+ #
+ # Constructs a new instance of {@link ParseTreeMatch} from the specified
+ # parse tree and pattern.
+ #
+ # @param tree The parse tree to match against the pattern.
+ # @param pattern The parse tree pattern.
+ # @param labels A mapping from label names to collections of
+ # {@link ParseTree} objects located by the tree pattern matching process.
+ # @param mismatchedNode The first node which failed to match the tree
+ # pattern during the matching process.
+ #
+ # @exception IllegalArgumentException if {@code tree} is {@code null}
+ # @exception IllegalArgumentException if {@code pattern} is {@code null}
+ # @exception IllegalArgumentException if {@code labels} is {@code null}
+ #
+ def __init__(self, tree:ParseTree, pattern:ParseTreePattern, labels:dict, mismatchedNode:ParseTree):
+ if tree is None:
+ raise Exception("tree cannot be null")
+ if pattern is None:
+ raise Exception("pattern cannot be null")
+ if labels is None:
+ raise Exception("labels cannot be null")
+ self.tree = tree
+ self.pattern = pattern
+ self.labels = labels
+ self.mismatchedNode = mismatchedNode
+
+ #
+ # Get the last node associated with a specific {@code label}.
+ #
+ #
For example, for pattern {@code }, {@code get("id")} returns the
+ # node matched for that {@code ID}. If more than one node
+ # matched the specified label, only the last is returned. If there is
+ # no node associated with the label, this returns {@code null}.
+ #
+ #
Pattern tags like {@code } and {@code } without labels are
+ # considered to be labeled with {@code ID} and {@code expr}, respectively.
+ #
+ # @param label The label to check.
+ #
+ # @return The last {@link ParseTree} to match a tag with the specified
+ # label, or {@code null} if no parse tree matched a tag with the label.
+ #
+ def get(self, label:str):
+ parseTrees = self.labels.get(label, None)
+ if parseTrees is None or len(parseTrees)==0:
+ return None
+ else:
+ return parseTrees[len(parseTrees)-1]
+
+ #
+ # Return all nodes matching a rule or token tag with the specified label.
+ #
+ #
If the {@code label} is the name of a parser rule or token in the
+ # grammar, the resulting list will contain both the parse trees matching
+ # rule or tags explicitly labeled with the label and the complete set of
+ # parse trees matching the labeled and unlabeled tags in the pattern for
+ # the parser rule or token. For example, if {@code label} is {@code "foo"},
+ # the result will contain all of the following.
+ #
+ #
+ #
Parse tree nodes matching tags of the form {@code } and
+ # {@code }.
+ #
Parse tree nodes matching tags of the form {@code }.
+ #
Parse tree nodes matching tags of the form {@code }.
+ #
+ #
+ # @param label The label.
+ #
+ # @return A collection of all {@link ParseTree} nodes matching tags with
+ # the specified {@code label}. If no nodes matched the label, an empty list
+ # is returned.
+ #
+ def getAll(self, label:str):
+ nodes = self.labels.get(label, None)
+ if nodes is None:
+ return list()
+ else:
+ return nodes
+
+
+ #
+ # Gets a value indicating whether the match operation succeeded.
+ #
+ # @return {@code true} if the match operation succeeded; otherwise,
+ # {@code false}.
+ #
+ def succeeded(self):
+ return self.mismatchedNode is None
+
+ #
+ # {@inheritDoc}
+ #
+ def __str__(self):
+ with StringIO() as buf:
+ buf.write("Match ")
+ buf.write("succeeded" if self.succeeded() else "failed")
+ buf.write("; found ")
+ buf.write(str(len(self.labels)))
+ buf.write(" labels")
+ return buf.getvalue()
diff --git a/cameras/deps/antlr4/tree/ParseTreePattern.py b/cameras/deps/antlr4/tree/ParseTreePattern.py
new file mode 100644
index 0000000000000000000000000000000000000000..37fd0bf09f478d47f927b3fdf7d7a32da1c0b795
--- /dev/null
+++ b/cameras/deps/antlr4/tree/ParseTreePattern.py
@@ -0,0 +1,72 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# A pattern like {@code = ;} converted to a {@link ParseTree} by
+# {@link ParseTreePatternMatcher#compile(String, int)}.
+#
+from antlr4.tree.ParseTreePatternMatcher import ParseTreePatternMatcher
+from antlr4.tree.Tree import ParseTree
+from antlr4.xpath.XPath import XPath
+
+
+class ParseTreePattern(object):
+ __slots__ = ('matcher', 'patternRuleIndex', 'pattern', 'patternTree')
+
+ # Construct a new instance of the {@link ParseTreePattern} class.
+ #
+ # @param matcher The {@link ParseTreePatternMatcher} which created this
+ # tree pattern.
+ # @param pattern The tree pattern in concrete syntax form.
+ # @param patternRuleIndex The parser rule which serves as the root of the
+ # tree pattern.
+ # @param patternTree The tree pattern in {@link ParseTree} form.
+ #
+ def __init__(self, matcher:ParseTreePatternMatcher, pattern:str, patternRuleIndex:int , patternTree:ParseTree):
+ self.matcher = matcher
+ self.patternRuleIndex = patternRuleIndex
+ self.pattern = pattern
+ self.patternTree = patternTree
+
+ #
+ # Match a specific parse tree against this tree pattern.
+ #
+ # @param tree The parse tree to match against this tree pattern.
+ # @return A {@link ParseTreeMatch} object describing the result of the
+ # match operation. The {@link ParseTreeMatch#succeeded()} method can be
+ # used to determine whether or not the match was successful.
+ #
+ def match(self, tree:ParseTree):
+ return self.matcher.match(tree, self)
+
+ #
+ # Determine whether or not a parse tree matches this tree pattern.
+ #
+ # @param tree The parse tree to match against this tree pattern.
+ # @return {@code true} if {@code tree} is a match for the current tree
+ # pattern; otherwise, {@code false}.
+ #
+ def matches(self, tree:ParseTree):
+ return self.matcher.match(tree, self).succeeded()
+
+ # Find all nodes using XPath and then try to match those subtrees against
+ # this tree pattern.
+ #
+ # @param tree The {@link ParseTree} to match against this pattern.
+ # @param xpath An expression matching the nodes
+ #
+ # @return A collection of {@link ParseTreeMatch} objects describing the
+ # successful matches. Unsuccessful matches are omitted from the result,
+ # regardless of the reason for the failure.
+ #
+ def findAll(self, tree:ParseTree, xpath:str):
+ subtrees = XPath.findAll(tree, xpath, self.matcher.parser)
+ matches = list()
+ for t in subtrees:
+ match = self.match(t)
+ if match.succeeded():
+ matches.append(match)
+ return matches
diff --git a/cameras/deps/antlr4/tree/ParseTreePatternMatcher.py b/cameras/deps/antlr4/tree/ParseTreePatternMatcher.py
new file mode 100644
index 0000000000000000000000000000000000000000..62fd197b0d143393fa187ead9b0c576112b486be
--- /dev/null
+++ b/cameras/deps/antlr4/tree/ParseTreePatternMatcher.py
@@ -0,0 +1,374 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# A tree pattern matching mechanism for ANTLR {@link ParseTree}s.
+#
+#
Patterns are strings of source input text with special tags representing
+# token or rule references such as:
+#
+#
{@code = ;}
+#
+#
Given a pattern start rule such as {@code statement}, this object constructs
+# a {@link ParseTree} with placeholders for the {@code ID} and {@code expr}
+# subtree. Then the {@link #match} routines can compare an actual
+# {@link ParseTree} from a parse with this pattern. Tag {@code } matches
+# any {@code ID} token and tag {@code } references the result of the
+# {@code expr} rule (generally an instance of {@code ExprContext}.
+#
+#
Pattern {@code x = 0;} is a similar pattern that matches the same pattern
+# except that it requires the identifier to be {@code x} and the expression to
+# be {@code 0}.
+#
+#
The {@link #matches} routines return {@code true} or {@code false} based
+# upon a match for the tree rooted at the parameter sent in. The
+# {@link #match} routines return a {@link ParseTreeMatch} object that
+# contains the parse tree, the parse tree pattern, and a map from tag name to
+# matched nodes (more below). A subtree that fails to match, returns with
+# {@link ParseTreeMatch#mismatchedNode} set to the first tree node that did not
+# match.
+#
+#
For efficiency, you can compile a tree pattern in string form to a
+# {@link ParseTreePattern} object.
+#
+#
See {@code TestParseTreeMatcher} for lots of examples.
+# {@link ParseTreePattern} has two static helper methods:
+# {@link ParseTreePattern#findAll} and {@link ParseTreePattern#match} that
+# are easy to use but not super efficient because they create new
+# {@link ParseTreePatternMatcher} objects each time and have to compile the
+# pattern in string form before using it.
+#
+#
The lexer and parser that you pass into the {@link ParseTreePatternMatcher}
+# constructor are used to parse the pattern in string form. The lexer converts
+# the {@code = ;} into a sequence of four tokens (assuming lexer
+# throws out whitespace or puts it on a hidden channel). Be aware that the
+# input stream is reset for the lexer (but not the parser; a
+# {@link ParserInterpreter} is created to parse the input.). Any user-defined
+# fields you have put into the lexer might get changed when this mechanism asks
+# it to scan the pattern string.
+#
+#
Normally a parser does not accept token {@code } as a valid
+# {@code expr} but, from the parser passed in, we create a special version of
+# the underlying grammar representation (an {@link ATN}) that allows imaginary
+# tokens representing rules ({@code }) to match entire rules. We call
+# these bypass alternatives.
+#
+#
Delimiters are {@code <} and {@code >}, with {@code \} as the escape string
+# by default, but you can set them to whatever you want using
+# {@link #setDelimiters}. You must escape both start and stop strings
+# {@code \<} and {@code \>}.
+#
+from antlr4.CommonTokenStream import CommonTokenStream
+from antlr4.InputStream import InputStream
+from antlr4.ParserRuleContext import ParserRuleContext
+from antlr4.Lexer import Lexer
+from antlr4.ListTokenSource import ListTokenSource
+from antlr4.Token import Token
+from antlr4.error.ErrorStrategy import BailErrorStrategy
+from antlr4.error.Errors import RecognitionException, ParseCancellationException
+from antlr4.tree.Chunk import TagChunk, TextChunk
+from antlr4.tree.RuleTagToken import RuleTagToken
+from antlr4.tree.TokenTagToken import TokenTagToken
+from antlr4.tree.Tree import ParseTree, TerminalNode, RuleNode
+
+# need forward declaration
+Parser = None
+ParseTreePattern = None
+
+class CannotInvokeStartRule(Exception):
+
+ def __init__(self, e:Exception):
+ super().__init__(e)
+
+class StartRuleDoesNotConsumeFullPattern(Exception):
+
+ pass
+
+
+class ParseTreePatternMatcher(object):
+ __slots__ = ('lexer', 'parser', 'start', 'stop', 'escape')
+
+ # Constructs a {@link ParseTreePatternMatcher} or from a {@link Lexer} and
+ # {@link Parser} object. The lexer input stream is altered for tokenizing
+ # the tree patterns. The parser is used as a convenient mechanism to get
+ # the grammar name, plus token, rule names.
+ def __init__(self, lexer:Lexer, parser:Parser):
+ self.lexer = lexer
+ self.parser = parser
+ self.start = "<"
+ self.stop = ">"
+ self.escape = "\\" # e.g., \< and \> must escape BOTH!
+
+ # Set the delimiters used for marking rule and token tags within concrete
+ # syntax used by the tree pattern parser.
+ #
+ # @param start The start delimiter.
+ # @param stop The stop delimiter.
+ # @param escapeLeft The escape sequence to use for escaping a start or stop delimiter.
+ #
+ # @exception IllegalArgumentException if {@code start} is {@code null} or empty.
+ # @exception IllegalArgumentException if {@code stop} is {@code null} or empty.
+ #
+ def setDelimiters(self, start:str, stop:str, escapeLeft:str):
+ if start is None or len(start)==0:
+ raise Exception("start cannot be null or empty")
+ if stop is None or len(stop)==0:
+ raise Exception("stop cannot be null or empty")
+ self.start = start
+ self.stop = stop
+ self.escape = escapeLeft
+
+ # Does {@code pattern} matched as rule {@code patternRuleIndex} match {@code tree}?#
+ def matchesRuleIndex(self, tree:ParseTree, pattern:str, patternRuleIndex:int):
+ p = self.compileTreePattern(pattern, patternRuleIndex)
+ return self.matches(tree, p)
+
+ # Does {@code pattern} matched as rule patternRuleIndex match tree? Pass in a
+ # compiled pattern instead of a string representation of a tree pattern.
+ #
+ def matchesPattern(self, tree:ParseTree, pattern:ParseTreePattern):
+ mismatchedNode = self.matchImpl(tree, pattern.patternTree, dict())
+ return mismatchedNode is None
+
+ #
+ # Compare {@code pattern} matched as rule {@code patternRuleIndex} against
+ # {@code tree} and return a {@link ParseTreeMatch} object that contains the
+ # matched elements, or the node at which the match failed.
+ #
+ def matchRuleIndex(self, tree:ParseTree, pattern:str, patternRuleIndex:int):
+ p = self.compileTreePattern(pattern, patternRuleIndex)
+ return self.matchPattern(tree, p)
+
+ #
+ # Compare {@code pattern} matched against {@code tree} and return a
+ # {@link ParseTreeMatch} object that contains the matched elements, or the
+ # node at which the match failed. Pass in a compiled pattern instead of a
+ # string representation of a tree pattern.
+ #
+ def matchPattern(self, tree:ParseTree, pattern:ParseTreePattern):
+ labels = dict()
+ mismatchedNode = self.matchImpl(tree, pattern.patternTree, labels)
+ from antlr4.tree.ParseTreeMatch import ParseTreeMatch
+ return ParseTreeMatch(tree, pattern, labels, mismatchedNode)
+
+ #
+ # For repeated use of a tree pattern, compile it to a
+ # {@link ParseTreePattern} using this method.
+ #
+ def compileTreePattern(self, pattern:str, patternRuleIndex:int):
+ tokenList = self.tokenize(pattern)
+ tokenSrc = ListTokenSource(tokenList)
+ tokens = CommonTokenStream(tokenSrc)
+ from antlr4.ParserInterpreter import ParserInterpreter
+ parserInterp = ParserInterpreter(self.parser.grammarFileName, self.parser.tokenNames,
+ self.parser.ruleNames, self.parser.getATNWithBypassAlts(),tokens)
+ tree = None
+ try:
+ parserInterp.setErrorHandler(BailErrorStrategy())
+ tree = parserInterp.parse(patternRuleIndex)
+ except ParseCancellationException as e:
+ raise e.cause
+ except RecognitionException as e:
+ raise e
+ except Exception as e:
+ raise CannotInvokeStartRule(e)
+
+ # Make sure tree pattern compilation checks for a complete parse
+ if tokens.LA(1)!=Token.EOF:
+ raise StartRuleDoesNotConsumeFullPattern()
+
+ from antlr4.tree.ParseTreePattern import ParseTreePattern
+ return ParseTreePattern(self, pattern, patternRuleIndex, tree)
+
+ #
+ # Recursively walk {@code tree} against {@code patternTree}, filling
+ # {@code match.}{@link ParseTreeMatch#labels labels}.
+ #
+ # @return the first node encountered in {@code tree} which does not match
+ # a corresponding node in {@code patternTree}, or {@code null} if the match
+ # was successful. The specific node returned depends on the matching
+ # algorithm used by the implementation, and may be overridden.
+ #
+ def matchImpl(self, tree:ParseTree, patternTree:ParseTree, labels:dict):
+ if tree is None:
+ raise Exception("tree cannot be null")
+ if patternTree is None:
+ raise Exception("patternTree cannot be null")
+
+ # x and , x and y, or x and x; or could be mismatched types
+ if isinstance(tree, TerminalNode) and isinstance(patternTree, TerminalNode ):
+ mismatchedNode = None
+ # both are tokens and they have same type
+ if tree.symbol.type == patternTree.symbol.type:
+ if isinstance( patternTree.symbol, TokenTagToken ): # x and
+ tokenTagToken = patternTree.symbol
+ # track label->list-of-nodes for both token name and label (if any)
+ self.map(labels, tokenTagToken.tokenName, tree)
+ if tokenTagToken.label is not None:
+ self.map(labels, tokenTagToken.label, tree)
+ elif tree.getText()==patternTree.getText():
+ # x and x
+ pass
+ else:
+ # x and y
+ if mismatchedNode is None:
+ mismatchedNode = tree
+ else:
+ if mismatchedNode is None:
+ mismatchedNode = tree
+
+ return mismatchedNode
+
+ if isinstance(tree, ParserRuleContext) and isinstance(patternTree, ParserRuleContext):
+ mismatchedNode = None
+ # (expr ...) and
+ ruleTagToken = self.getRuleTagToken(patternTree)
+ if ruleTagToken is not None:
+ m = None
+ if tree.ruleContext.ruleIndex == patternTree.ruleContext.ruleIndex:
+ # track label->list-of-nodes for both rule name and label (if any)
+ self.map(labels, ruleTagToken.ruleName, tree)
+ if ruleTagToken.label is not None:
+ self.map(labels, ruleTagToken.label, tree)
+ else:
+ if mismatchedNode is None:
+ mismatchedNode = tree
+
+ return mismatchedNode
+
+ # (expr ...) and (expr ...)
+ if tree.getChildCount()!=patternTree.getChildCount():
+ if mismatchedNode is None:
+ mismatchedNode = tree
+ return mismatchedNode
+
+ n = tree.getChildCount()
+ for i in range(0, n):
+ childMatch = self.matchImpl(tree.getChild(i), patternTree.getChild(i), labels)
+ if childMatch is not None:
+ return childMatch
+
+ return mismatchedNode
+
+ # if nodes aren't both tokens or both rule nodes, can't match
+ return tree
+
+ def map(self, labels, label, tree):
+ v = labels.get(label, None)
+ if v is None:
+ v = list()
+ labels[label] = v
+ v.append(tree)
+
+ # Is {@code t} {@code (expr )} subtree?#
+ def getRuleTagToken(self, tree:ParseTree):
+ if isinstance( tree, RuleNode ):
+ if tree.getChildCount()==1 and isinstance(tree.getChild(0), TerminalNode ):
+ c = tree.getChild(0)
+ if isinstance( c.symbol, RuleTagToken ):
+ return c.symbol
+ return None
+
+ def tokenize(self, pattern:str):
+ # split pattern into chunks: sea (raw input) and islands (, )
+ chunks = self.split(pattern)
+
+ # create token stream from text and tags
+ tokens = list()
+ for chunk in chunks:
+ if isinstance( chunk, TagChunk ):
+ # add special rule token or conjure up new token from name
+ if chunk.tag[0].isupper():
+ ttype = self.parser.getTokenType(chunk.tag)
+ if ttype==Token.INVALID_TYPE:
+ raise Exception("Unknown token " + str(chunk.tag) + " in pattern: " + pattern)
+ tokens.append(TokenTagToken(chunk.tag, ttype, chunk.label))
+ elif chunk.tag[0].islower():
+ ruleIndex = self.parser.getRuleIndex(chunk.tag)
+ if ruleIndex==-1:
+ raise Exception("Unknown rule " + str(chunk.tag) + " in pattern: " + pattern)
+ ruleImaginaryTokenType = self.parser.getATNWithBypassAlts().ruleToTokenType[ruleIndex]
+ tokens.append(RuleTagToken(chunk.tag, ruleImaginaryTokenType, chunk.label))
+ else:
+ raise Exception("invalid tag: " + str(chunk.tag) + " in pattern: " + pattern)
+ else:
+ self.lexer.setInputStream(InputStream(chunk.text))
+ t = self.lexer.nextToken()
+ while t.type!=Token.EOF:
+ tokens.append(t)
+ t = self.lexer.nextToken()
+ return tokens
+
+ # Split {@code = ;} into 4 chunks for tokenizing by {@link #tokenize}.#
+ def split(self, pattern:str):
+ p = 0
+ n = len(pattern)
+ chunks = list()
+ # find all start and stop indexes first, then collect
+ starts = list()
+ stops = list()
+ while p < n :
+ if p == pattern.find(self.escape + self.start, p):
+ p += len(self.escape) + len(self.start)
+ elif p == pattern.find(self.escape + self.stop, p):
+ p += len(self.escape) + len(self.stop)
+ elif p == pattern.find(self.start, p):
+ starts.append(p)
+ p += len(self.start)
+ elif p == pattern.find(self.stop, p):
+ stops.append(p)
+ p += len(self.stop)
+ else:
+ p += 1
+
+ nt = len(starts)
+
+ if nt > len(stops):
+ raise Exception("unterminated tag in pattern: " + pattern)
+ if nt < len(stops):
+ raise Exception("missing start tag in pattern: " + pattern)
+
+ for i in range(0, nt):
+ if starts[i] >= stops[i]:
+ raise Exception("tag delimiters out of order in pattern: " + pattern)
+
+ # collect into chunks now
+ if nt==0:
+ chunks.append(TextChunk(pattern))
+
+ if nt>0 and starts[0]>0: # copy text up to first tag into chunks
+ text = pattern[0:starts[0]]
+ chunks.add(TextChunk(text))
+
+ for i in range(0, nt):
+ # copy inside of
+ tag = pattern[starts[i] + len(self.start) : stops[i]]
+ ruleOrToken = tag
+ label = None
+ colon = tag.find(':')
+ if colon >= 0:
+ label = tag[0:colon]
+ ruleOrToken = tag[colon+1 : len(tag)]
+ chunks.append(TagChunk(label, ruleOrToken))
+ if i+1 < len(starts):
+ # copy from end of to start of next
+ text = pattern[stops[i] + len(self.stop) : starts[i + 1]]
+ chunks.append(TextChunk(text))
+
+ if nt > 0 :
+ afterLastTag = stops[nt - 1] + len(self.stop)
+ if afterLastTag < n : # copy text from end of last tag to end
+ text = pattern[afterLastTag : n]
+ chunks.append(TextChunk(text))
+
+ # strip out the escape sequences from text chunks but not tags
+ for i in range(0, len(chunks)):
+ c = chunks[i]
+ if isinstance( c, TextChunk ):
+ unescaped = c.text.replace(self.escape, "")
+ if len(unescaped) < len(c.text):
+ chunks[i] = TextChunk(unescaped)
+ return chunks
diff --git a/cameras/deps/antlr4/tree/RuleTagToken.py b/cameras/deps/antlr4/tree/RuleTagToken.py
new file mode 100644
index 0000000000000000000000000000000000000000..a198f7da13643d538ce96aeeb6a8ff4f757f1ecd
--- /dev/null
+++ b/cameras/deps/antlr4/tree/RuleTagToken.py
@@ -0,0 +1,50 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# A {@link Token} object representing an entire subtree matched by a parser
+# rule; e.g., {@code }. These tokens are created for {@link TagChunk}
+# chunks where the tag corresponds to a parser rule.
+#
+from antlr4.Token import Token
+
+
+class RuleTagToken(Token):
+ __slots__ = ('label', 'ruleName')
+ #
+ # Constructs a new instance of {@link RuleTagToken} with the specified rule
+ # name, bypass token type, and label.
+ #
+ # @param ruleName The name of the parser rule this rule tag matches.
+ # @param bypassTokenType The bypass token type assigned to the parser rule.
+ # @param label The label associated with the rule tag, or {@code null} if
+ # the rule tag is unlabeled.
+ #
+ # @exception IllegalArgumentException if {@code ruleName} is {@code null}
+ # or empty.
+
+ def __init__(self, ruleName:str, bypassTokenType:int, label:str=None):
+ if ruleName is None or len(ruleName)==0:
+ raise Exception("ruleName cannot be null or empty.")
+ self.source = None
+ self.type = bypassTokenType # token type of the token
+ self.channel = Token.DEFAULT_CHANNEL # The parser ignores everything not on DEFAULT_CHANNEL
+ self.start = -1 # optional; return -1 if not implemented.
+ self.stop = -1 # optional; return -1 if not implemented.
+ self.tokenIndex = -1 # from 0..n-1 of the token object in the input stream
+ self.line = 0 # line=1..n of the 1st character
+ self.column = -1 # beginning of the line at which it occurs, 0..n-1
+ self.label = label
+ self._text = self.getText() # text of the token.
+
+ self.ruleName = ruleName
+
+
+ def getText(self):
+ if self.label is None:
+ return "<" + self.ruleName + ">"
+ else:
+ return "<" + self.label + ":" + self.ruleName + ">"
diff --git a/cameras/deps/antlr4/tree/TokenTagToken.py b/cameras/deps/antlr4/tree/TokenTagToken.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7beeb87684c06606e17053f0f74fcae36876959
--- /dev/null
+++ b/cameras/deps/antlr4/tree/TokenTagToken.py
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# A {@link Token} object representing a token of a particular type; e.g.,
+# {@code }. These tokens are created for {@link TagChunk} chunks where the
+# tag corresponds to a lexer rule or token type.
+#
+from antlr4.Token import CommonToken
+
+
+class TokenTagToken(CommonToken):
+ __slots__ = ('tokenName', 'label')
+ # Constructs a new instance of {@link TokenTagToken} with the specified
+ # token name, type, and label.
+ #
+ # @param tokenName The token name.
+ # @param type The token type.
+ # @param label The label associated with the token tag, or {@code null} if
+ # the token tag is unlabeled.
+ #
+ def __init__(self, tokenName:str, type:int, label:str=None):
+ super().__init__(type=type)
+ self.tokenName = tokenName
+ self.label = label
+ self._text = self.getText()
+
+ #
+ # {@inheritDoc}
+ #
+ #
The implementation for {@link TokenTagToken} returns the token tag
+ # formatted with {@code <} and {@code >} delimiters.
The implementation for {@link TokenTagToken} returns a string of the form
+ # {@code tokenName:type}.
+ #
+ def __str__(self):
+ return self.tokenName + ":" + str(self.type)
diff --git a/cameras/deps/antlr4/tree/Tree.py b/cameras/deps/antlr4/tree/Tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..812acc96bbee97860bc8a914feedcd0584def050
--- /dev/null
+++ b/cameras/deps/antlr4/tree/Tree.py
@@ -0,0 +1,191 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+
+# The basic notion of a tree has a parent, a payload, and a list of children.
+# It is the most abstract interface for all the trees used by ANTLR.
+#/
+from antlr4.Token import Token
+
+INVALID_INTERVAL = (-1, -2)
+
+class Tree(object):
+ pass
+
+class SyntaxTree(Tree):
+ pass
+
+class ParseTree(SyntaxTree):
+ pass
+
+class RuleNode(ParseTree):
+ pass
+
+class TerminalNode(ParseTree):
+ pass
+
+class ErrorNode(TerminalNode):
+ pass
+
+class ParseTreeVisitor(object):
+ def visit(self, tree):
+ return tree.accept(self)
+
+ def visitChildren(self, node):
+ result = self.defaultResult()
+ n = node.getChildCount()
+ for i in range(n):
+ if not self.shouldVisitNextChild(node, result):
+ return result
+
+ c = node.getChild(i)
+ childResult = c.accept(self)
+ result = self.aggregateResult(result, childResult)
+
+ return result
+
+ def visitTerminal(self, node):
+ return self.defaultResult()
+
+ def visitErrorNode(self, node):
+ return self.defaultResult()
+
+ def defaultResult(self):
+ return None
+
+ def aggregateResult(self, aggregate, nextResult):
+ return nextResult
+
+ def shouldVisitNextChild(self, node, currentResult):
+ return True
+
+ParserRuleContext = None
+
+class ParseTreeListener(object):
+
+ def visitTerminal(self, node:TerminalNode):
+ pass
+
+ def visitErrorNode(self, node:ErrorNode):
+ pass
+
+ def enterEveryRule(self, ctx:ParserRuleContext):
+ pass
+
+ def exitEveryRule(self, ctx:ParserRuleContext):
+ pass
+
+del ParserRuleContext
+
+class TerminalNodeImpl(TerminalNode):
+ __slots__ = ('parentCtx', 'symbol')
+
+ def __init__(self, symbol:Token):
+ self.parentCtx = None
+ self.symbol = symbol
+ def __setattr__(self, key, value):
+ super().__setattr__(key, value)
+
+ def getChild(self, i:int):
+ return None
+
+ def getSymbol(self):
+ return self.symbol
+
+ def getParent(self):
+ return self.parentCtx
+
+ def getPayload(self):
+ return self.symbol
+
+ def getSourceInterval(self):
+ if self.symbol is None:
+ return INVALID_INTERVAL
+ tokenIndex = self.symbol.tokenIndex
+ return (tokenIndex, tokenIndex)
+
+ def getChildCount(self):
+ return 0
+
+ def accept(self, visitor:ParseTreeVisitor):
+ return visitor.visitTerminal(self)
+
+ def getText(self):
+ return self.symbol.text
+
+ def __str__(self):
+ if self.symbol.type == Token.EOF:
+ return ""
+ else:
+ return self.symbol.text
+
+# Represents a token that was consumed during resynchronization
+# rather than during a valid match operation. For example,
+# we will create this kind of a node during single token insertion
+# and deletion as well as during "consume until error recovery set"
+# upon no viable alternative exceptions.
+
+class ErrorNodeImpl(TerminalNodeImpl,ErrorNode):
+
+ def __init__(self, token:Token):
+ super().__init__(token)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ return visitor.visitErrorNode(self)
+
+
+class ParseTreeWalker(object):
+
+ DEFAULT = None
+
+ def walk(self, listener:ParseTreeListener, t:ParseTree):
+ """
+ Performs a walk on the given parse tree starting at the root and going down recursively
+ with depth-first search. On each node, {@link ParseTreeWalker#enterRule} is called before
+ recursively walking down into child nodes, then
+ {@link ParseTreeWalker#exitRule} is called after the recursive call to wind up.
+ @param listener The listener used by the walker to process grammar rules
+ @param t The parse tree to be walked on
+ """
+ if isinstance(t, ErrorNode):
+ listener.visitErrorNode(t)
+ return
+ elif isinstance(t, TerminalNode):
+ listener.visitTerminal(t)
+ return
+ self.enterRule(listener, t)
+ for child in t.getChildren():
+ self.walk(listener, child)
+ self.exitRule(listener, t)
+
+ #
+ # The discovery of a rule node, involves sending two events: the generic
+ # {@link ParseTreeListener#enterEveryRule} and a
+ # {@link RuleContext}-specific event. First we trigger the generic and then
+ # the rule specific. We to them in reverse order upon finishing the node.
+ #
+ def enterRule(self, listener:ParseTreeListener, r:RuleNode):
+ """
+ Enters a grammar rule by first triggering the generic event {@link ParseTreeListener#enterEveryRule}
+ then by triggering the event specific to the given parse tree node
+ @param listener The listener responding to the trigger events
+ @param r The grammar rule containing the rule context
+ """
+ ctx = r.getRuleContext()
+ listener.enterEveryRule(ctx)
+ ctx.enterRule(listener)
+
+ def exitRule(self, listener:ParseTreeListener, r:RuleNode):
+ """
+ Exits a grammar rule by first triggering the event specific to the given parse tree node
+ then by triggering the generic event {@link ParseTreeListener#exitEveryRule}
+ @param listener The listener responding to the trigger events
+ @param r The grammar rule containing the rule context
+ """
+ ctx = r.getRuleContext()
+ ctx.exitRule(listener)
+ listener.exitEveryRule(ctx)
+
+ParseTreeWalker.DEFAULT = ParseTreeWalker()
diff --git a/cameras/deps/antlr4/tree/Trees.py b/cameras/deps/antlr4/tree/Trees.py
new file mode 100644
index 0000000000000000000000000000000000000000..686b8cb287b3058c2e0b33dfb1567320299f214e
--- /dev/null
+++ b/cameras/deps/antlr4/tree/Trees.py
@@ -0,0 +1,111 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+
+# A set of utility routines useful for all kinds of ANTLR trees.#
+from io import StringIO
+from antlr4.Token import Token
+from antlr4.Utils import escapeWhitespace
+from antlr4.tree.Tree import RuleNode, ErrorNode, TerminalNode, Tree, ParseTree
+
+# need forward declaration
+Parser = None
+
+class Trees(object):
+
+ # Print out a whole tree in LISP form. {@link #getNodeText} is used on the
+ # node payloads to get the text for the nodes. Detect
+ # parse trees and extract data appropriately.
+ @classmethod
+ def toStringTree(cls, t:Tree, ruleNames:list=None, recog:Parser=None):
+ if recog is not None:
+ ruleNames = recog.ruleNames
+ s = escapeWhitespace(cls.getNodeText(t, ruleNames), False)
+ if t.getChildCount()==0:
+ return s
+ with StringIO() as buf:
+ buf.write("(")
+ buf.write(s)
+ buf.write(' ')
+ for i in range(0, t.getChildCount()):
+ if i > 0:
+ buf.write(' ')
+ buf.write(cls.toStringTree(t.getChild(i), ruleNames))
+ buf.write(")")
+ return buf.getvalue()
+
+ @classmethod
+ def getNodeText(cls, t:Tree, ruleNames:list=None, recog:Parser=None):
+ if recog is not None:
+ ruleNames = recog.ruleNames
+ if ruleNames is not None:
+ if isinstance(t, RuleNode):
+ if t.getAltNumber()!=0: # should use ATN.INVALID_ALT_NUMBER but won't compile
+ return ruleNames[t.getRuleIndex()]+":"+str(t.getAltNumber())
+ return ruleNames[t.getRuleIndex()]
+ elif isinstance( t, ErrorNode):
+ return str(t)
+ elif isinstance(t, TerminalNode):
+ if t.symbol is not None:
+ return t.symbol.text
+ # no recog for rule names
+ payload = t.getPayload()
+ if isinstance(payload, Token ):
+ return payload.text
+ return str(t.getPayload())
+
+
+ # Return ordered list of all children of this node
+ @classmethod
+ def getChildren(cls, t:Tree):
+ return [ t.getChild(i) for i in range(0, t.getChildCount()) ]
+
+ # Return a list of all ancestors of this node. The first node of
+ # list is the root and the last is the parent of this node.
+ #
+ @classmethod
+ def getAncestors(cls, t:Tree):
+ ancestors = []
+ t = t.getParent()
+ while t is not None:
+ ancestors.insert(0, t) # insert at start
+ t = t.getParent()
+ return ancestors
+
+ @classmethod
+ def findAllTokenNodes(cls, t:ParseTree, ttype:int):
+ return cls.findAllNodes(t, ttype, True)
+
+ @classmethod
+ def findAllRuleNodes(cls, t:ParseTree, ruleIndex:int):
+ return cls.findAllNodes(t, ruleIndex, False)
+
+ @classmethod
+ def findAllNodes(cls, t:ParseTree, index:int, findTokens:bool):
+ nodes = []
+ cls._findAllNodes(t, index, findTokens, nodes)
+ return nodes
+
+ @classmethod
+ def _findAllNodes(cls, t:ParseTree, index:int, findTokens:bool, nodes:list):
+ from antlr4.ParserRuleContext import ParserRuleContext
+ # check this node (the root) first
+ if findTokens and isinstance(t, TerminalNode):
+ if t.symbol.type==index:
+ nodes.append(t)
+ elif not findTokens and isinstance(t, ParserRuleContext):
+ if t.ruleIndex == index:
+ nodes.append(t)
+ # check children
+ for i in range(0, t.getChildCount()):
+ cls._findAllNodes(t.getChild(i), index, findTokens, nodes)
+
+ @classmethod
+ def descendants(cls, t:ParseTree):
+ nodes = [t]
+ for i in range(0, t.getChildCount()):
+ nodes.extend(cls.descendants(t.getChild(i)))
+ return nodes
diff --git a/cameras/deps/antlr4/tree/__init__.py b/cameras/deps/antlr4/tree/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/cameras/deps/antlr4/xpath/XPath.py b/cameras/deps/antlr4/xpath/XPath.py
new file mode 100644
index 0000000000000000000000000000000000000000..24029e7cf2bfa9798538211bc7599b49dbb70191
--- /dev/null
+++ b/cameras/deps/antlr4/xpath/XPath.py
@@ -0,0 +1,352 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# Represent a subset of XPath XML path syntax for use in identifying nodes in
+# parse trees.
+#
+#
+# Split path into words and separators {@code /} and {@code //} via ANTLR
+# itself then walk path elements from left to right. At each separator-word
+# pair, find set of nodes. Next stage uses those as work list.
+#
+#
+# The basic interface is
+# {@link XPath#findAll ParseTree.findAll}{@code (tree, pathString, parser)}.
+# But that is just shorthand for:
+#
+#
+# {@link XPath} p = new {@link XPath#XPath XPath}(parser, pathString);
+# return p.{@link #evaluate evaluate}(tree);
+#
+#
+#
+# See {@code org.antlr.v4.test.TestXPath} for descriptions. In short, this
+# allows operators:
+#
+#
+#
/
root
+#
//
anywhere
+#
!
invert; this must appear directly after root or anywhere
+# operator
+#
+#
+#
+# and path elements:
+#
+#
+#
ID
token name
+#
'string'
any string literal token from the grammar
+#
expr
rule name
+#
*
wildcard matching any node
+#
+#
+#
+# Whitespace is not allowed.
+#
+from antlr4 import CommonTokenStream, DFA, PredictionContextCache, Lexer, LexerATNSimulator, ParserRuleContext, TerminalNode
+from antlr4.InputStream import InputStream
+from antlr4.Parser import Parser
+from antlr4.RuleContext import RuleContext
+from antlr4.Token import Token
+from antlr4.atn.ATNDeserializer import ATNDeserializer
+from antlr4.error.ErrorListener import ErrorListener
+from antlr4.error.Errors import LexerNoViableAltException
+from antlr4.tree.Tree import ParseTree
+from antlr4.tree.Trees import Trees
+from io import StringIO
+
+
+def serializedATN():
+ with StringIO() as buf:
+ buf.write("\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\2\n")
+ buf.write("\64\b\1\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t")
+ buf.write("\7\4\b\t\b\4\t\t\t\3\2\3\2\3\2\3\3\3\3\3\4\3\4\3\5\3\5")
+ buf.write("\3\6\3\6\7\6\37\n\6\f\6\16\6\"\13\6\3\6\3\6\3\7\3\7\5")
+ buf.write("\7(\n\7\3\b\3\b\3\t\3\t\7\t.\n\t\f\t\16\t\61\13\t\3\t")
+ buf.write("\3\t\3/\2\n\3\5\5\6\7\7\t\b\13\t\r\2\17\2\21\n\3\2\4\7")
+ buf.write("\2\62;aa\u00b9\u00b9\u0302\u0371\u2041\u2042\17\2C\\c")
+ buf.write("|\u00c2\u00d8\u00da\u00f8\u00fa\u0301\u0372\u037f\u0381")
+ buf.write("\u2001\u200e\u200f\u2072\u2191\u2c02\u2ff1\u3003\ud801")
+ buf.write("\uf902\ufdd1\ufdf2\uffff\64\2\3\3\2\2\2\2\5\3\2\2\2\2")
+ buf.write("\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2\21\3\2\2\2\3\23")
+ buf.write("\3\2\2\2\5\26\3\2\2\2\7\30\3\2\2\2\t\32\3\2\2\2\13\34")
+ buf.write("\3\2\2\2\r\'\3\2\2\2\17)\3\2\2\2\21+\3\2\2\2\23\24\7\61")
+ buf.write("\2\2\24\25\7\61\2\2\25\4\3\2\2\2\26\27\7\61\2\2\27\6\3")
+ buf.write("\2\2\2\30\31\7,\2\2\31\b\3\2\2\2\32\33\7#\2\2\33\n\3\2")
+ buf.write("\2\2\34 \5\17\b\2\35\37\5\r\7\2\36\35\3\2\2\2\37\"\3\2")
+ buf.write("\2\2 \36\3\2\2\2 !\3\2\2\2!#\3\2\2\2\" \3\2\2\2#$\b\6")
+ buf.write("\2\2$\f\3\2\2\2%(\5\17\b\2&(\t\2\2\2\'%\3\2\2\2\'&\3\2")
+ buf.write("\2\2(\16\3\2\2\2)*\t\3\2\2*\20\3\2\2\2+/\7)\2\2,.\13\2")
+ buf.write("\2\2-,\3\2\2\2.\61\3\2\2\2/\60\3\2\2\2/-\3\2\2\2\60\62")
+ buf.write("\3\2\2\2\61/\3\2\2\2\62\63\7)\2\2\63\22\3\2\2\2\6\2 \'")
+ buf.write("/\3\3\6\2")
+ return buf.getvalue()
+
+
+class XPathLexer(Lexer):
+
+ atn = ATNDeserializer().deserialize(serializedATN())
+
+ decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
+
+
+ TOKEN_REF = 1
+ RULE_REF = 2
+ ANYWHERE = 3
+ ROOT = 4
+ WILDCARD = 5
+ BANG = 6
+ ID = 7
+ STRING = 8
+
+ modeNames = [ "DEFAULT_MODE" ]
+
+ literalNames = [ "",
+ "'//'", "'/'", "'*'", "'!'" ]
+
+ symbolicNames = [ "",
+ "TOKEN_REF", "RULE_REF", "ANYWHERE", "ROOT", "WILDCARD", "BANG",
+ "ID", "STRING" ]
+
+ ruleNames = [ "ANYWHERE", "ROOT", "WILDCARD", "BANG", "ID", "NameChar",
+ "NameStartChar", "STRING" ]
+
+ grammarFileName = "XPathLexer.g4"
+
+ def __init__(self, input=None):
+ super().__init__(input)
+ self.checkVersion("4.9.1")
+ self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache())
+ self._actions = None
+ self._predicates = None
+
+
+ def action(self, localctx:RuleContext, ruleIndex:int, actionIndex:int):
+ if self._actions is None:
+ actions = dict()
+ actions[4] = self.ID_action
+ self._actions = actions
+ _action = self._actions.get(ruleIndex, None)
+ if _action is not None:
+ _action(localctx, actionIndex)
+ else:
+ raise Exception("No registered action for: %d" % ruleIndex)
+
+ def ID_action(self, localctx:RuleContext , actionIndex:int):
+ if actionIndex == 0:
+ char = self.text[0]
+ if char.isupper():
+ self.type = XPathLexer.TOKEN_REF
+ else:
+ self.type = XPathLexer.RULE_REF
+
+class XPath(object):
+
+ WILDCARD = "*" # word not operator/separator
+ NOT = "!" # word for invert operator
+
+ def __init__(self, parser:Parser, path:str):
+ self.parser = parser
+ self.path = path
+ self.elements = self.split(path)
+
+ def split(self, path:str):
+ input = InputStream(path)
+ lexer = XPathLexer(input)
+ def recover(self, e):
+ raise e
+ lexer.recover = recover
+ lexer.removeErrorListeners()
+ lexer.addErrorListener(ErrorListener()) # XPathErrorListener does no more
+ tokenStream = CommonTokenStream(lexer)
+ try:
+ tokenStream.fill()
+ except LexerNoViableAltException as e:
+ pos = lexer.column
+ msg = "Invalid tokens or characters at index %d in path '%s'" % (pos, path)
+ raise Exception(msg, e)
+
+ tokens = iter(tokenStream.tokens)
+ elements = list()
+ for el in tokens:
+ invert = False
+ anywhere = False
+ # Check for path separators, if none assume root
+ if el.type in [XPathLexer.ROOT, XPathLexer.ANYWHERE]:
+ anywhere = el.type == XPathLexer.ANYWHERE
+ next_el = next(tokens, None)
+ if not next_el:
+ raise Exception('Missing element after %s' % el.getText())
+ else:
+ el = next_el
+ # Check for bangs
+ if el.type == XPathLexer.BANG:
+ invert = True
+ next_el = next(tokens, None)
+ if not next_el:
+ raise Exception('Missing element after %s' % el.getText())
+ else:
+ el = next_el
+ # Add searched element
+ if el.type in [XPathLexer.TOKEN_REF, XPathLexer.RULE_REF, XPathLexer.WILDCARD, XPathLexer.STRING]:
+ element = self.getXPathElement(el, anywhere)
+ element.invert = invert
+ elements.append(element)
+ elif el.type==Token.EOF:
+ break
+ else:
+ raise Exception("Unknown path element %s" % lexer.symbolicNames[el.type])
+ return elements
+
+ #
+ # Convert word like {@code#} or {@code ID} or {@code expr} to a path
+ # element. {@code anywhere} is {@code true} if {@code //} precedes the
+ # word.
+ #
+ def getXPathElement(self, wordToken:Token, anywhere:bool):
+ if wordToken.type==Token.EOF:
+ raise Exception("Missing path element at end of path")
+
+ word = wordToken.text
+ if wordToken.type==XPathLexer.WILDCARD :
+ return XPathWildcardAnywhereElement() if anywhere else XPathWildcardElement()
+
+ elif wordToken.type in [XPathLexer.TOKEN_REF, XPathLexer.STRING]:
+ tsource = self.parser.getTokenStream().tokenSource
+
+ ttype = Token.INVALID_TYPE
+ if wordToken.type == XPathLexer.TOKEN_REF:
+ if word in tsource.ruleNames:
+ ttype = tsource.ruleNames.index(word) + 1
+ else:
+ if word in tsource.literalNames:
+ ttype = tsource.literalNames.index(word)
+
+ if ttype == Token.INVALID_TYPE:
+ raise Exception("%s at index %d isn't a valid token name" % (word, wordToken.tokenIndex))
+ return XPathTokenAnywhereElement(word, ttype) if anywhere else XPathTokenElement(word, ttype)
+
+ else:
+ ruleIndex = self.parser.ruleNames.index(word) if word in self.parser.ruleNames else -1
+
+ if ruleIndex == -1:
+ raise Exception("%s at index %d isn't a valid rule name" % (word, wordToken.tokenIndex))
+ return XPathRuleAnywhereElement(word, ruleIndex) if anywhere else XPathRuleElement(word, ruleIndex)
+
+
+ @staticmethod
+ def findAll(tree:ParseTree, xpath:str, parser:Parser):
+ p = XPath(parser, xpath)
+ return p.evaluate(tree)
+
+ #
+ # Return a list of all nodes starting at {@code t} as root that satisfy the
+ # path. The root {@code /} is relative to the node passed to
+ # {@link #evaluate}.
+ #
+ def evaluate(self, t:ParseTree):
+ dummyRoot = ParserRuleContext()
+ dummyRoot.children = [t] # don't set t's parent.
+
+ work = [dummyRoot]
+ for element in self.elements:
+ work_next = list()
+ for node in work:
+ if not isinstance(node, TerminalNode) and node.children:
+ # only try to match next element if it has children
+ # e.g., //func/*/stat might have a token node for which
+ # we can't go looking for stat nodes.
+ matching = element.evaluate(node)
+
+ # See issue antlr#370 - Prevents XPath from returning the
+ # same node multiple times
+ matching = filter(lambda m: m not in work_next, matching)
+
+ work_next.extend(matching)
+ work = work_next
+
+ return work
+
+
+class XPathElement(object):
+
+ def __init__(self, nodeName:str):
+ self.nodeName = nodeName
+ self.invert = False
+
+ def __str__(self):
+ return type(self).__name__ + "[" + ("!" if self.invert else "") + self.nodeName + "]"
+
+
+
+#
+# Either {@code ID} at start of path or {@code ...//ID} in middle of path.
+#
+class XPathRuleAnywhereElement(XPathElement):
+
+ def __init__(self, ruleName:str, ruleIndex:int):
+ super().__init__(ruleName)
+ self.ruleIndex = ruleIndex
+
+ def evaluate(self, t:ParseTree):
+ # return all ParserRuleContext descendants of t that match ruleIndex (or do not match if inverted)
+ return filter(lambda c: isinstance(c, ParserRuleContext) and (self.invert ^ (c.getRuleIndex() == self.ruleIndex)), Trees.descendants(t))
+
+class XPathRuleElement(XPathElement):
+
+ def __init__(self, ruleName:str, ruleIndex:int):
+ super().__init__(ruleName)
+ self.ruleIndex = ruleIndex
+
+ def evaluate(self, t:ParseTree):
+ # return all ParserRuleContext children of t that match ruleIndex (or do not match if inverted)
+ return filter(lambda c: isinstance(c, ParserRuleContext) and (self.invert ^ (c.getRuleIndex() == self.ruleIndex)), Trees.getChildren(t))
+
+class XPathTokenAnywhereElement(XPathElement):
+
+ def __init__(self, ruleName:str, tokenType:int):
+ super().__init__(ruleName)
+ self.tokenType = tokenType
+
+ def evaluate(self, t:ParseTree):
+ # return all TerminalNode descendants of t that match tokenType (or do not match if inverted)
+ return filter(lambda c: isinstance(c, TerminalNode) and (self.invert ^ (c.symbol.type == self.tokenType)), Trees.descendants(t))
+
+class XPathTokenElement(XPathElement):
+
+ def __init__(self, ruleName:str, tokenType:int):
+ super().__init__(ruleName)
+ self.tokenType = tokenType
+
+ def evaluate(self, t:ParseTree):
+ # return all TerminalNode children of t that match tokenType (or do not match if inverted)
+ return filter(lambda c: isinstance(c, TerminalNode) and (self.invert ^ (c.symbol.type == self.tokenType)), Trees.getChildren(t))
+
+
+class XPathWildcardAnywhereElement(XPathElement):
+
+ def __init__(self):
+ super().__init__(XPath.WILDCARD)
+
+ def evaluate(self, t:ParseTree):
+ if self.invert:
+ return list() # !* is weird but valid (empty)
+ else:
+ return Trees.descendants(t)
+
+
+class XPathWildcardElement(XPathElement):
+
+ def __init__(self):
+ super().__init__(XPath.WILDCARD)
+
+
+ def evaluate(self, t:ParseTree):
+ if self.invert:
+ return list() # !* is weird but valid (empty)
+ else:
+ return Trees.getChildren(t)
diff --git a/cameras/deps/antlr4/xpath/__init__.py b/cameras/deps/antlr4/xpath/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..216c000dc5ffc8e53cc9c596e420c1e67604d1aa
--- /dev/null
+++ b/cameras/deps/antlr4/xpath/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ericvergnaud'
diff --git a/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/INSTALLER b/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/METADATA b/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..987b1859b0967dabb32c13e3b245e6ea8dfbf7df
--- /dev/null
+++ b/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/METADATA
@@ -0,0 +1,15 @@
+Metadata-Version: 2.4
+Name: antlr4-python3-runtime
+Version: 4.9.3
+Summary: ANTLR 4.9.3 runtime for Python 3.7
+Home-page: http://www.antlr.org
+Author: Eric Vergnaud, Terence Parr, Sam Harwell
+Author-email: eric.vergnaud@wanadoo.fr
+License: BSD
+Requires-Dist: typing; python_version < "3.5"
+Dynamic: author
+Dynamic: author-email
+Dynamic: home-page
+Dynamic: license
+Dynamic: requires-dist
+Dynamic: summary
diff --git a/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/RECORD b/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..058473e34966823a6806dd8fba68c44c35c53b3d
--- /dev/null
+++ b/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/RECORD
@@ -0,0 +1,63 @@
+../../bin/pygrun,sha256=OQXTUTdDCOCsNDmN0z0y0CSLY_o2mHH2SWgE56c2yHU,6305
+antlr4/BufferedTokenStream.py,sha256=_BwmzOH1TO6yL2yC_ZaUzkghq8wzc0UPHfI3UpnZUwM,10780
+antlr4/CommonTokenFactory.py,sha256=Tv16zg_pWD1Dv3IphsxFu8nwWdLeXYcqJ8CC5yHwjH8,2110
+antlr4/CommonTokenStream.py,sha256=NNJHXwRg2_Zn46ZhJyDxZtvZzsPWhb6JjXa7BjM45eg,2770
+antlr4/FileStream.py,sha256=-ZR_-jl_If9IIBYLINIwlQrlTSmu5k1VUKDc3ie7WR4,868
+antlr4/InputStream.py,sha256=sggjE2jEGvSgQmxFvqeeuT3aOVgcH5tS7mMybW8wKS4,2334
+antlr4/IntervalSet.py,sha256=Cd0WKhd_kYbiLYKkDNncgSM19GAuS7OaTOC4-5Yubs4,5965
+antlr4/LL1Analyzer.py,sha256=oJBvO7_S8cAlb_D4qWNxd2IlK0qP4ka-oeoDxx16CZ4,7752
+antlr4/Lexer.py,sha256=C72hqayfkympxb46AcSnhPD9kVZ0quWgboGxa6gcIcg,11542
+antlr4/ListTokenSource.py,sha256=IffLMo7YQnD_CjKryrrgNWSk0q5QSYd7puZyyUk7vOk,5356
+antlr4/Parser.py,sha256=F2Q25z0-__KHfa354KQhDu3ZOVzLFfag3s2ixJ4dl_o,22883
+antlr4/ParserInterpreter.py,sha256=-QU9kn4x3WCQ-LSA99R231HoicTqakiHZ5KM72l-hIo,7206
+antlr4/ParserRuleContext.py,sha256=wHAVdOxMAO5jkUqloTXVzn_xYnJhiHbvvuhZpth0ZF8,6762
+antlr4/PredictionContext.py,sha256=cb4KI6EGpS7sRzJ8UvPEkxphINZuWhyiZ95752g3prI,22977
+antlr4/Recognizer.py,sha256=vmKAtSjIgR9LQr5YzuK5OmPZWMJ3x69OuVZQ_FTzQHE,5383
+antlr4/RuleContext.py,sha256=GiviRv2k_al1IBgdJOEEoD0ohJaVd-_h5T_CPG_Bsmg,8099
+antlr4/StdinStream.py,sha256=MMSH4zN8T6i_nu-3_TlN-3E4nPM4b5KgK4GT6n_FUQA,303
+antlr4/Token.py,sha256=OtWCab4Ut52X_nLLAA-8x4Zl6xaF6TEN-0033uaoaEo,5206
+antlr4/TokenStreamRewriter.py,sha256=cuErQTrXwC_0kqVv3MsTWGZSm-E1Vy1yzA-3SOhKd_s,10324
+antlr4/Utils.py,sha256=Oyg8CJCRL1TrF_QSB_LLlVdWOB4loVcKOgFNT-icO7c,931
+antlr4/__init__.py,sha256=g8UGpflnlMWcAyLtihejzrgAP1Uo3b9GhwfI8QnZjtw,1125
+antlr4/atn/ATN.py,sha256=LYE8kT-D8FpUd5fpOtyOLqvXLFkUSa83TVFowhCWAiY,5789
+antlr4/atn/ATNConfig.py,sha256=tNdIC6_GrxXllHBx3npAWyDh6KrohLZDV_XyPrydRMY,6565
+antlr4/atn/ATNConfigSet.py,sha256=qRzVsBeMqk2txjG3DrGptwF6Vb2hHC5w3umkSL0GNJw,8312
+antlr4/atn/ATNDeserializationOptions.py,sha256=lUV_bGW6mxj7t20esda5Yv-X9m-U_x1-0xaLifhXIPo,1010
+antlr4/atn/ATNDeserializer.py,sha256=aYLDDtQ-wyo3gId6A-wD1E3QmpfrPZlXxj4_IDm-mUY,22252
+antlr4/atn/ATNSimulator.py,sha256=mDc-G3GF3kSeqpfGDabUOLJ0WLVTqibxZlkvXQYmBRk,2298
+antlr4/atn/ATNState.py,sha256=NbndISWUwFDF_vuBfbTiZZ8GPHoQa6UXdqbD-yjJE7c,7663
+antlr4/atn/ATNType.py,sha256=xgv8AMVU7tc07U73_hRTm1AiZ7MvGhoaP5fTiOrrCGg,422
+antlr4/atn/LexerATNSimulator.py,sha256=kYXRwUvHptSRU8T_K9pSrGlCk9YypWeHlAcjgry1VVo,25465
+antlr4/atn/LexerAction.py,sha256=KUeJwKekBch0m1poSPskHIh-15dcKAG4lR7zlq98tzc,10014
+antlr4/atn/LexerActionExecutor.py,sha256=7rlg17THcwLsuTmh7NsLrTbRH4DTrm8qIdW9_235CEc,6420
+antlr4/atn/ParserATNSimulator.py,sha256=IKCzsDLcznROSVojU-daAygKr3svl0DmK5DhkUllASY,80365
+antlr4/atn/PredictionMode.py,sha256=i8B7MULA7v-qbXeCY_xp6sgi21kHM6kybqIrG6rSrro,22486
+antlr4/atn/SemanticContext.py,sha256=ds0TmM4qenb0LN-rl2Fp_N_xB959abN67I19EF6rs8o,10495
+antlr4/atn/Transition.py,sha256=ZAsEFpa5I_n-zxD6U-DauM5_33jFK65x3PWu6-NW0RA,8762
+antlr4/atn/__init__.py,sha256=gsnQdtTH8IUgCiVUpQfzhxx2pFRvksW76SjwIk3fYSk,28
+antlr4/dfa/DFA.py,sha256=weIh0uaRfakP12mFvHo7U0tqO3GONV3-nHFkc2xk-ZE,5388
+antlr4/dfa/DFASerializer.py,sha256=1st_HO85yXLYy7gInTEnkztgA6am4CT-yReh-mazp9E,2518
+antlr4/dfa/DFAState.py,sha256=R7JwKf0GtAEs9J_MD_Y0WKcuzdt0BVX1sow-uv9yFYc,5583
+antlr4/dfa/__init__.py,sha256=gsnQdtTH8IUgCiVUpQfzhxx2pFRvksW76SjwIk3fYSk,28
+antlr4/error/DiagnosticErrorListener.py,sha256=EwS2D_Ox6CmvCa16NPJ9ud4QYPHmlPXt6-Wdn1h5Kg8,4430
+antlr4/error/ErrorListener.py,sha256=yP_MDguol4Cj0_pEPyNzeH3v4ZvUjW5iwDjhYTVAHbE,2722
+antlr4/error/ErrorStrategy.py,sha256=0mhzFL57ZVnjKkGrtadta93Zm3NXdF-HW10DVD07VXs,30391
+antlr4/error/Errors.py,sha256=hlKngclBfXdkDiAymhYsvh2OCXlvmHM2kTl_A1vgp-w,6759
+antlr4/error/__init__.py,sha256=gsnQdtTH8IUgCiVUpQfzhxx2pFRvksW76SjwIk3fYSk,28
+antlr4/tree/Chunk.py,sha256=oCIZjolLq9xkxtVDROEDxfUGgndcEnsDW0eUmLM7Gpk,695
+antlr4/tree/ParseTreeMatch.py,sha256=Dc6GVWSUqoIAFXUaUZqUwCUlZfTcgUbGLGzNf6QxQvE,4485
+antlr4/tree/ParseTreePattern.py,sha256=ASBNaQORh3f7f8KnFeZJC2yWFFx4uQlxvC2Y55ifhY0,2825
+antlr4/tree/ParseTreePatternMatcher.py,sha256=HtE9yi1Urr2QPLGLJDBvr0lxv6bjuj9CHl-4clahSe8,16388
+antlr4/tree/RuleTagToken.py,sha256=n4zXcmrrfsGyl91pj5ZYcc_CeKMhPrvYkUdppgMBpbY,2022
+antlr4/tree/TokenTagToken.py,sha256=S3o3DJhfzL5kpClxsKyI-Il-xvuuZQiBAIsLCKFjRHo,1576
+antlr4/tree/Tree.py,sha256=ZI7U_5IxBLm_IrnfJOtb12BCPIWyzfeZtLnhHKVVZIw,5572
+antlr4/tree/Trees.py,sha256=JtQ7cYWmKwI9TIBP6y9XIgjlNS4mYjv3ARwOfwWc5Vg,3968
+antlr4/tree/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+antlr4/xpath/XPath.py,sha256=O9s4-EDvLbAbYytP_bae9Z2khLl0iAtRzPAtVbuWUM4,13015
+antlr4/xpath/__init__.py,sha256=gsnQdtTH8IUgCiVUpQfzhxx2pFRvksW76SjwIk3fYSk,28
+antlr4_python3_runtime-4.9.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+antlr4_python3_runtime-4.9.3.dist-info/METADATA,sha256=0d5cdBaEGlaj1zeV5_MFh8K487oB-7_2jAlOMIxinn0,418
+antlr4_python3_runtime-4.9.3.dist-info/RECORD,,
+antlr4_python3_runtime-4.9.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+antlr4_python3_runtime-4.9.3.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
+antlr4_python3_runtime-4.9.3.dist-info/top_level.txt,sha256=OsoZsh9bb30wgXb2zBUjdDwYg46MfV-RVZA6Pk8pcB0,7
diff --git a/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/REQUESTED b/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/WHEEL b/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..1eb3c49d99559863120cfb8433fc8738fba43ba9
--- /dev/null
+++ b/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: setuptools (78.1.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/top_level.txt b/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3dee19f3e13c2cbbb49b818e6ac67c63f77b865f
--- /dev/null
+++ b/cameras/deps/antlr4_python3_runtime-4.9.3.dist-info/top_level.txt
@@ -0,0 +1 @@
+antlr4
diff --git a/cameras/deps/bin/pygrun b/cameras/deps/bin/pygrun
new file mode 100644
index 0000000000000000000000000000000000000000..8c44020b8af686d2466bd62ecf9f92e1c8e1ec4c
--- /dev/null
+++ b/cameras/deps/bin/pygrun
@@ -0,0 +1,164 @@
+#!C:\LumenGen\engine\python\python.exe
+__author__ = 'jszheng'
+import optparse
+import sys
+import os
+import importlib
+from antlr4 import *
+
+
+# this is a python version of TestRig
+def beautify_lisp_string(in_string):
+ indent_size = 3
+ add_indent = ' '*indent_size
+ out_string = in_string[0] # no indent for 1st (
+ indent = ''
+ for i in range(1, len(in_string)):
+ if in_string[i] == '(' and in_string[i+1] != ' ':
+ indent += add_indent
+ out_string += "\n" + indent + '('
+ elif in_string[i] == ')':
+ out_string += ')'
+ if len(indent) > 0:
+ indent = indent.replace(add_indent, '', 1)
+ else:
+ out_string += in_string[i]
+ return out_string
+
+
+if __name__ == '__main__':
+
+ #############################################################
+ # parse options
+ # not support -gui -encoding -ps
+ #############################################################
+ usage = "Usage: %prog [options] Grammar_Name Start_Rule"
+ parser = optparse.OptionParser(usage=usage)
+ # parser.add_option('-t', '--tree',
+ # dest="out_file",
+ # default="default.out",
+ # help='set output file name',
+ # )
+ parser.add_option('-t', '--tree',
+ default=False,
+ action='store_true',
+ help='Print AST tree'
+ )
+ parser.add_option('-k', '--tokens',
+ dest="token",
+ default=False,
+ action='store_true',
+ help='Show Tokens'
+ )
+ parser.add_option('-s', '--sll',
+ dest="sll",
+ default=False,
+ action='store_true',
+ help='Show SLL'
+ )
+ parser.add_option('-d', '--diagnostics',
+ dest="diagnostics",
+ default=False,
+ action='store_true',
+ help='Enable diagnostics error listener'
+ )
+ parser.add_option('-a', '--trace',
+ dest="trace",
+ default=False,
+ action='store_true',
+ help='Enable Trace'
+ )
+
+ options, remainder = parser.parse_args()
+ if len(remainder) < 2:
+ print('ERROR: You have to provide at least 2 arguments!')
+ parser.print_help()
+ exit(1)
+ else:
+ grammar = remainder.pop(0)
+ start_rule = remainder.pop(0)
+ file_list = remainder
+
+ #############################################################
+ # check and load antlr generated files
+ #############################################################
+ # dynamic load the module and class
+ lexerName = grammar + 'Lexer'
+ parserName = grammar + 'Parser'
+ # check if the generate file exist
+ lexer_file = lexerName + '.py'
+ parser_file = parserName + '.py'
+ if not os.path.exists(lexer_file):
+ print("[ERROR] Can't find lexer file {}!".format(lexer_file))
+ print(os.path.realpath('.'))
+ exit(1)
+ if not os.path.exists(parser_file):
+ print("[ERROR] Can't find parser file {}!".format(lexer_file))
+ print(os.path.realpath('.'))
+ exit(1)
+
+ # current directory is where the generated file loaded
+ # the script might be in different place.
+ sys.path.append('.')
+ # print(sys.path)
+
+ # print("Load Lexer {}".format(lexerName))
+ module_lexer = __import__(lexerName, globals(), locals(), lexerName)
+ class_lexer = getattr(module_lexer, lexerName)
+ # print(class_lexer)
+
+ # print("Load Parser {}".format(parserName))
+ module_parser = __import__(parserName, globals(), locals(), parserName)
+ class_parser = getattr(module_parser, parserName)
+ # print(class_parser)
+
+ #############################################################
+ # main process steps.
+ #############################################################
+ def process(input_stream, class_lexer, class_parser):
+ lexer = class_lexer(input_stream)
+ token_stream = CommonTokenStream(lexer)
+ token_stream.fill()
+ if options.token: # need to show token
+ for tok in token_stream.tokens:
+ print(tok)
+ if start_rule == 'tokens':
+ return
+
+ parser = class_parser(token_stream)
+
+ if options.diagnostics:
+ parser.addErrorListener(DiagnosticErrorListener())
+ parser._interp.predictionMode = PredictionMode.LL_EXACT_AMBIG_DETECTION
+ if options.tree:
+ parser.buildParseTrees = True
+ if options.sll:
+ parser._interp.predictionMode = PredictionMode.SLL
+ #parser.setTokenStream(token_stream)
+ parser.setTrace(options.trace)
+ if hasattr(parser, start_rule):
+ func_start_rule = getattr(parser, start_rule)
+ parser_ret = func_start_rule()
+ if options.tree:
+ lisp_tree_str = parser_ret.toStringTree(recog=parser)
+ print(beautify_lisp_string(lisp_tree_str))
+ else:
+ print("[ERROR] Can't find start rule '{}' in parser '{}'".format(start_rule, parserName))
+
+ #############################################################
+ # use stdin if not provide file as input stream
+ #############################################################
+ if len(file_list) == 0:
+ input_stream = InputStream(sys.stdin.read())
+ process(input_stream, class_lexer, class_parser)
+ exit(0)
+
+ #############################################################
+ # iterate all input file
+ #############################################################
+ for file_name in file_list:
+ if os.path.exists(file_name) and os.path.isfile(file_name):
+ input_stream = FileStream(file_name)
+ process(input_stream, class_lexer, class_parser)
+ else:
+ print("[ERROR] file {} not exist".format(os.path.normpath(file_name)))
diff --git a/cameras/deps/omegaconf-2.3.0.dist-info/INSTALLER b/cameras/deps/omegaconf-2.3.0.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/cameras/deps/omegaconf-2.3.0.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/cameras/deps/omegaconf-2.3.0.dist-info/LICENSE b/cameras/deps/omegaconf-2.3.0.dist-info/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..eb208dada03bbd9975633d6f42eb036c09e33125
--- /dev/null
+++ b/cameras/deps/omegaconf-2.3.0.dist-info/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2018, Omry Yadan
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cameras/deps/omegaconf-2.3.0.dist-info/METADATA b/cameras/deps/omegaconf-2.3.0.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..6d3ab67e16a7dfc446c5e7f878569d4fa7e79d6e
--- /dev/null
+++ b/cameras/deps/omegaconf-2.3.0.dist-info/METADATA
@@ -0,0 +1,64 @@
+Metadata-Version: 2.1
+Name: omegaconf
+Version: 2.3.0
+Summary: A flexible configuration library
+Home-page: https://github.com/omry/omegaconf
+Author: Omry Yadan
+Author-email: omry@yadan.net
+Keywords: yaml configuration config
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Requires-Python: >=3.6
+Description-Content-Type: text/markdown
+License-File: LICENSE
+Requires-Dist: antlr4-python3-runtime (==4.9.*)
+Requires-Dist: PyYAML (>=5.1.0)
+Requires-Dist: dataclasses ; python_version == "3.6"
+
+# OmegaConf
+| | Description |
+| --- | --- |
+| Project | [](https://badge.fury.io/py/omegaconf)[](https://pepy.tech/project/omegaconf?versions=1.4.*&versions=2.0.*&versions=2.1.*) |
+| Code quality| [](https://circleci.com/gh/omry/omegaconf)[](https://coveralls.io/github/omry/omegaconf)[](https://lgtm.com/projects/g/omry/omegaconf/alerts/)[](https://lgtm.com/projects/g/omry/omegaconf/context:python)|
+| Docs and support |[](https://omegaconf.readthedocs.io/en/2.1_branch/)[](https://mybinder.org/v2/gh/omry/omegaconf/master?filepath=docs%2Fnotebook%2FTutorial.ipynb)|
+
+
+OmegaConf is a hierarchical configuration system, with support for merging configurations from multiple sources (YAML config files, dataclasses/objects and CLI arguments)
+providing a consistent API regardless of how the configuration was created.
+
+## Releases
+
+### Stable (2.2)
+OmegaConf 2.2 is the current stable version.
+* [What's new](https://github.com/omry/omegaconf/releases/tag/v2.2.1)
+* [Documentation](https://omegaconf.readthedocs.io/en/2.2_branch/)
+* [Source code](https://github.com/omry/omegaconf/tree/2.2_branch)
+
+Install with `pip install --upgrade omegaconf`
+
+### Previous release (2.1)
+OmegaConf 2.1 is the current stable version.
+* [What's new](https://github.com/omry/omegaconf/releases/tag/v2.1.1)
+* [Documentation](https://omegaconf.readthedocs.io/en/2.1_branch/)
+* [Slides](https://docs.google.com/presentation/d/e/2PACX-1vT_UIV7hCnquIbLUm4NnkUpXvPEh33IKiUEvPRF850WKA8opOlZOszjKdZ3tPmf8u7hGNP6HpqS-NT5/pub?start=false&loop=false&delayms=3000)
+* [Source code](https://github.com/omry/omegaconf/tree/2.1_branch)
+
+Install with `pip install omegaconf==2.1`
+
+### Previous release (2.0)
+
+* [What's new](https://github.com/omry/omegaconf/releases/tag/v2.0.0)
+* [Documentation](https://omegaconf.readthedocs.io/en/2.0_branch/)
+* [Slides](https://docs.google.com/presentation/d/e/2PACX-1vT_UIV7hCnquIbLUm4NnkUpXvPEh33IKiUEvPRF850WKA8opOlZOszjKdZ3tPmf8u7hGNP6HpqS-NT5/pub?start=false&loop=false&delayms=3000)
+* [Source code](https://github.com/omry/omegaconf/tree/2.0_branch)
+
+Install with `pip install omegaconf==2.0.6`
+
+## Live tutorial
+Run the live tutorial: [](https://mybinder.org/v2/gh/omry/omegaconf/master?filepath=docs%2Fnotebook%2FTutorial.ipynb)
diff --git a/cameras/deps/omegaconf-2.3.0.dist-info/RECORD b/cameras/deps/omegaconf-2.3.0.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..1c5adebfa6242ef8c53593dd3498b03a65f914cd
--- /dev/null
+++ b/cameras/deps/omegaconf-2.3.0.dist-info/RECORD
@@ -0,0 +1,33 @@
+omegaconf-2.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+omegaconf-2.3.0.dist-info/LICENSE,sha256=sxN17V1cvn7C0MTBTfivp6jlUBu-JfcLbLxGFoX0uRo,1518
+omegaconf-2.3.0.dist-info/METADATA,sha256=qy3L8OPnDMddyh2JT4lT596LZR3C3CjwjzUL-leHuts,3865
+omegaconf-2.3.0.dist-info/RECORD,,
+omegaconf-2.3.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+omegaconf-2.3.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
+omegaconf-2.3.0.dist-info/top_level.txt,sha256=gKo5sjlMnML5r9-l1eLYBTS1xZ-xAr67mlBSgL9yvSA,25
+omegaconf/__init__.py,sha256=lynwiDHEFBIHMqrBN3HRh0KJevzIap6MyULFNkMmlBc,1170
+omegaconf/_impl.py,sha256=CJfcRt8K20BiMMW5u3q8wuQ9hAeAmuYlFxctzPgTIgQ,2877
+omegaconf/_utils.py,sha256=bw8Jpra0B8YkHFrwODrBcKPvgQTyU0pEpskLl7CDBRg,32564
+omegaconf/base.py,sha256=rHWuLw8Iiew_SNJIUkLZy4EfyRb60hyS9X9Hl4OuBvs,32462
+omegaconf/basecontainer.py,sha256=FWL2PZWRb7wP-AGpeN651l_uVnNhZvzzLjaUdDlJiB8,33318
+omegaconf/dictconfig.py,sha256=IPtUPHoak025Q-gvDVqMdS497Xb6tjcsawrHXv4iQ2Y,27855
+omegaconf/errors.py,sha256=ectbX8Kd-5FCJMTBIh6jshqvCnCiXMFGfFQJYGIereQ,3595
+omegaconf/grammar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+omegaconf/grammar/gen/OmegaConfGrammarLexer.py,sha256=eoog1aofOltw-u61QFeCWZ3A9oa6vK2tjTaX4wmJY14,21689
+omegaconf/grammar/gen/OmegaConfGrammarParser.py,sha256=X0uJc0OieVB_f9yKRZw4OfwRBPOGoiyDMjhz8OKPvZ4,63429
+omegaconf/grammar/gen/OmegaConfGrammarParserListener.py,sha256=ncibh-Nhg68pdJPdMaqeFTP876wvo0lpJqPXhaOmjZU,5843
+omegaconf/grammar/gen/OmegaConfGrammarParserVisitor.py,sha256=Amvi244RtYFebNSJCbOwsqdvQW-QgdDKTku0rXnkD5Y,3617
+omegaconf/grammar/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+omegaconf/grammar_parser.py,sha256=7gvIGsqtsgl5e0XOWhI3Ur58FoIk3iBaX_M8JdyzpYM,5511
+omegaconf/grammar_visitor.py,sha256=L6LOomG24GcPA1-aYzNOmKLup5kYirHTuyp857V7T7A,16055
+omegaconf/listconfig.py,sha256=LyNRrn84BqNiz-TpwrDILat8HhlVwwM6zFmYCTUAdIo,24671
+omegaconf/nodes.py,sha256=1XRIC8sfEoaV4PT_Vm7Z75JMsIc30kKTuJHGyZ2aM0w,17479
+omegaconf/omegaconf.py,sha256=hFeVL0k6PNRqGE7ZUpI2p6sSGPkc9IEvsqr3MNdgU_s,39048
+omegaconf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+omegaconf/resolvers/__init__.py,sha256=zE-8WJwipGC2cXPq12CGAz49m82F1D1cKMwuV3EoT7k,60
+omegaconf/resolvers/oc/__init__.py,sha256=gLniKaouCmkm1ZTQOzS1F5jFlCMw7eL-ae6PhaMxNjg,3548
+omegaconf/resolvers/oc/dict.py,sha256=OL7iZ_aBIt7kYXxhMiFxSyskiX-xXBvGI_DnBbdxqVs,2324
+omegaconf/version.py,sha256=9OuwPWmsrNRBQYmeipe5XnywKY4jw-ZQBrjbo5C5Md4,442
+pydevd_plugins/__init__.py,sha256=byyA3PmxQsWCnqpQzFy44heJq59wOqHr5bSt6knm5vY,180
+pydevd_plugins/extensions/__init__.py,sha256=byyA3PmxQsWCnqpQzFy44heJq59wOqHr5bSt6knm5vY,180
+pydevd_plugins/extensions/pydevd_plugin_omegaconf.py,sha256=Ac1SCniO-22CsRsMAXhFyyoVSN2R-MUWsOujm-601Ak,4251
diff --git a/cameras/deps/omegaconf-2.3.0.dist-info/REQUESTED b/cameras/deps/omegaconf-2.3.0.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/cameras/deps/omegaconf-2.3.0.dist-info/WHEEL b/cameras/deps/omegaconf-2.3.0.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..57e3d840d59a650ac5bccbad5baeec47d155f0ad
--- /dev/null
+++ b/cameras/deps/omegaconf-2.3.0.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.38.4)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/cameras/deps/omegaconf-2.3.0.dist-info/top_level.txt b/cameras/deps/omegaconf-2.3.0.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..fcd2b36b800878631de7b82d0218569415c6a323
--- /dev/null
+++ b/cameras/deps/omegaconf-2.3.0.dist-info/top_level.txt
@@ -0,0 +1,2 @@
+omegaconf
+pydevd_plugins
diff --git a/cameras/deps/omegaconf/__init__.py b/cameras/deps/omegaconf/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1670cf08fa977560c2ad1ef2d98910824c1bdc9b
--- /dev/null
+++ b/cameras/deps/omegaconf/__init__.py
@@ -0,0 +1,65 @@
+from .base import Container, DictKeyType, Node, SCMode, UnionNode
+from .dictconfig import DictConfig
+from .errors import (
+ KeyValidationError,
+ MissingMandatoryValue,
+ ReadonlyConfigError,
+ UnsupportedValueType,
+ ValidationError,
+)
+from .listconfig import ListConfig
+from .nodes import (
+ AnyNode,
+ BooleanNode,
+ BytesNode,
+ EnumNode,
+ FloatNode,
+ IntegerNode,
+ PathNode,
+ StringNode,
+ ValueNode,
+)
+from .omegaconf import (
+ II,
+ MISSING,
+ SI,
+ OmegaConf,
+ Resolver,
+ flag_override,
+ open_dict,
+ read_write,
+)
+from .version import __version__
+
+__all__ = [
+ "__version__",
+ "MissingMandatoryValue",
+ "ValidationError",
+ "ReadonlyConfigError",
+ "UnsupportedValueType",
+ "KeyValidationError",
+ "Container",
+ "UnionNode",
+ "ListConfig",
+ "DictConfig",
+ "DictKeyType",
+ "OmegaConf",
+ "Resolver",
+ "SCMode",
+ "flag_override",
+ "read_write",
+ "open_dict",
+ "Node",
+ "ValueNode",
+ "AnyNode",
+ "IntegerNode",
+ "StringNode",
+ "BytesNode",
+ "PathNode",
+ "BooleanNode",
+ "EnumNode",
+ "FloatNode",
+ "MISSING",
+ "SI",
+ "II",
+]
diff --git a/cameras/deps/omegaconf/_impl.py b/cameras/deps/omegaconf/_impl.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c30ef6a239e65302c484b361773a00f85c70909
--- /dev/null
+++ b/cameras/deps/omegaconf/_impl.py
@@ -0,0 +1,101 @@
+from typing import Any
+
+from omegaconf import MISSING, Container, DictConfig, ListConfig, Node, ValueNode
+from omegaconf.errors import ConfigTypeError, InterpolationToMissingValueError
+
+from ._utils import _DEFAULT_MARKER_, _get_value
+
+
+def _resolve_container_value(cfg: Container, key: Any) -> None:
+ node = cfg._get_child(key)
+ assert isinstance(node, Node)
+ if node._is_interpolation():
+ try:
+ resolved = node._dereference_node()
+ except InterpolationToMissingValueError:
+ node._set_value(MISSING)
+ else:
+ if isinstance(resolved, Container):
+ _resolve(resolved)
+ if isinstance(resolved, Container) and isinstance(node, ValueNode):
+ cfg[key] = resolved
+ else:
+ node._set_value(_get_value(resolved))
+ else:
+ _resolve(node)
+
+
+def _resolve(cfg: Node) -> Node:
+ assert isinstance(cfg, Node)
+ if cfg._is_interpolation():
+ try:
+ resolved = cfg._dereference_node()
+ except InterpolationToMissingValueError:
+ cfg._set_value(MISSING)
+ else:
+ cfg._set_value(resolved._value())
+
+ if isinstance(cfg, DictConfig):
+ for k in cfg.keys():
+ _resolve_container_value(cfg, k)
+
+ elif isinstance(cfg, ListConfig):
+ for i in range(len(cfg)):
+ _resolve_container_value(cfg, i)
+
+ return cfg
+
+
+def select_value(
+ cfg: Container,
+ key: str,
+ *,
+ default: Any = _DEFAULT_MARKER_,
+ throw_on_resolution_failure: bool = True,
+ throw_on_missing: bool = False,
+ absolute_key: bool = False,
+) -> Any:
+ node = select_node(
+ cfg=cfg,
+ key=key,
+ throw_on_resolution_failure=throw_on_resolution_failure,
+ throw_on_missing=throw_on_missing,
+ absolute_key=absolute_key,
+ )
+
+ node_not_found = node is None
+ if node_not_found or node._is_missing():
+ if default is not _DEFAULT_MARKER_:
+ return default
+ else:
+ return None
+
+ return _get_value(node)
+
+
+def select_node(
+ cfg: Container,
+ key: str,
+ *,
+ throw_on_resolution_failure: bool = True,
+ throw_on_missing: bool = False,
+ absolute_key: bool = False,
+) -> Any:
+ try:
+ # for non relative keys, the interpretation can be:
+ # 1. relative to cfg
+ # 2. relative to the config root
+ # This is controlled by the absolute_key flag. By default, such keys are relative to cfg.
+ if not absolute_key and not key.startswith("."):
+ key = f".{key}"
+
+ cfg, key = cfg._resolve_key_and_root(key)
+ _root, _last_key, node = cfg._select_impl(
+ key,
+ throw_on_missing=throw_on_missing,
+ throw_on_resolution_failure=throw_on_resolution_failure,
+ )
+ except ConfigTypeError:
+ return None
+
+ return node
diff --git a/cameras/deps/omegaconf/_utils.py b/cameras/deps/omegaconf/_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..ffec246a38d278831bce26c6f7c233b72f34c1fe
--- /dev/null
+++ b/cameras/deps/omegaconf/_utils.py
@@ -0,0 +1,1039 @@
+import copy
+import os
+import pathlib
+import re
+import string
+import sys
+import types
+import warnings
+from contextlib import contextmanager
+from enum import Enum
+from textwrap import dedent
+from typing import (
+ Any,
+ Dict,
+ Iterator,
+ List,
+ Optional,
+ Tuple,
+ Type,
+ Union,
+ get_type_hints,
+)
+
+import yaml
+
+from .errors import (
+ ConfigIndexError,
+ ConfigTypeError,
+ ConfigValueError,
+ GrammarParseError,
+ OmegaConfBaseException,
+ ValidationError,
+)
+from .grammar_parser import SIMPLE_INTERPOLATION_PATTERN, parse
+
+try:
+ import dataclasses
+
+except ImportError: # pragma: no cover
+ dataclasses = None # type: ignore # pragma: no cover
+
+try:
+ import attr
+
+except ImportError: # pragma: no cover
+ attr = None # type: ignore # pragma: no cover
+
+NoneType: Type[None] = type(None)
+
+BUILTIN_VALUE_TYPES: Tuple[Type[Any], ...] = (
+ int,
+ float,
+ bool,
+ str,
+ bytes,
+ NoneType,
+)
+
+# Regexprs to match key paths like: a.b, a[b], ..a[c].d, etc.
+# We begin by matching the head (in these examples: a, a, ..a).
+# This can be read as "dots followed by any character but `.` or `[`"
+# Note that a key starting with brackets, like [a], is purposedly *not*
+# matched here and will instead be handled in the next regex below (this
+# is to keep this regex simple).
+KEY_PATH_HEAD = re.compile(r"(\.)*[^.[]*")
+# Then we match other keys. The following expression matches one key and can
+# be read as a choice between two syntaxes:
+# - `.` followed by anything except `.` or `[` (ex: .b, .d)
+# - `[` followed by anything then `]` (ex: [b], [c])
+KEY_PATH_OTHER = re.compile(r"\.([^.[]*)|\[(.*?)\]")
+
+
+# source: https://yaml.org/type/bool.html
+YAML_BOOL_TYPES = [
+ "y",
+ "Y",
+ "yes",
+ "Yes",
+ "YES",
+ "n",
+ "N",
+ "no",
+ "No",
+ "NO",
+ "true",
+ "True",
+ "TRUE",
+ "false",
+ "False",
+ "FALSE",
+ "on",
+ "On",
+ "ON",
+ "off",
+ "Off",
+ "OFF",
+]
+
+
+class Marker:
+ def __init__(self, desc: str):
+ self.desc = desc
+
+ def __repr__(self) -> str:
+ return self.desc
+
+
+# To be used as default value when `None` is not an option.
+_DEFAULT_MARKER_: Any = Marker("_DEFAULT_MARKER_")
+
+
+class OmegaConfDumper(yaml.Dumper): # type: ignore
+ str_representer_added = False
+
+ @staticmethod
+ def str_representer(dumper: yaml.Dumper, data: str) -> yaml.ScalarNode:
+ with_quotes = yaml_is_bool(data) or is_int(data) or is_float(data)
+ return dumper.represent_scalar(
+ yaml.resolver.BaseResolver.DEFAULT_SCALAR_TAG,
+ data,
+ style=("'" if with_quotes else None),
+ )
+
+
+def get_omega_conf_dumper() -> Type[OmegaConfDumper]:
+ if not OmegaConfDumper.str_representer_added:
+ OmegaConfDumper.add_representer(str, OmegaConfDumper.str_representer)
+ OmegaConfDumper.str_representer_added = True
+ return OmegaConfDumper
+
+
+def yaml_is_bool(b: str) -> bool:
+ return b in YAML_BOOL_TYPES
+
+
+def get_yaml_loader() -> Any:
+ class OmegaConfLoader(yaml.SafeLoader): # type: ignore
+ def construct_mapping(self, node: yaml.Node, deep: bool = False) -> Any:
+ keys = set()
+ for key_node, value_node in node.value:
+ if key_node.tag != yaml.resolver.BaseResolver.DEFAULT_SCALAR_TAG:
+ continue
+ if key_node.value in keys:
+ raise yaml.constructor.ConstructorError(
+ "while constructing a mapping",
+ node.start_mark,
+ f"found duplicate key {key_node.value}",
+ key_node.start_mark,
+ )
+ keys.add(key_node.value)
+ return super().construct_mapping(node, deep=deep)
+
+ loader = OmegaConfLoader
+ loader.add_implicit_resolver(
+ "tag:yaml.org,2002:float",
+ re.compile(
+ """^(?:
+ [-+]?[0-9]+(?:_[0-9]+)*\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
+ |[-+]?[0-9]+(?:_[0-9]+)*(?:[eE][-+]?[0-9]+)
+ |\\.[0-9]+(?:_[0-9]+)*(?:[eE][-+][0-9]+)?
+ |[-+]?[0-9]+(?:_[0-9]+)*(?::[0-5]?[0-9])+\\.[0-9_]*
+ |[-+]?\\.(?:inf|Inf|INF)
+ |\\.(?:nan|NaN|NAN))$""",
+ re.X,
+ ),
+ list("-+0123456789."),
+ )
+ loader.yaml_implicit_resolvers = {
+ key: [
+ (tag, regexp)
+ for tag, regexp in resolvers
+ if tag != "tag:yaml.org,2002:timestamp"
+ ]
+ for key, resolvers in loader.yaml_implicit_resolvers.items()
+ }
+
+ loader.add_constructor(
+ "tag:yaml.org,2002:python/object/apply:pathlib.Path",
+ lambda loader, node: pathlib.Path(*loader.construct_sequence(node)),
+ )
+ loader.add_constructor(
+ "tag:yaml.org,2002:python/object/apply:pathlib.PosixPath",
+ lambda loader, node: pathlib.PosixPath(*loader.construct_sequence(node)),
+ )
+ loader.add_constructor(
+ "tag:yaml.org,2002:python/object/apply:pathlib.WindowsPath",
+ lambda loader, node: pathlib.WindowsPath(*loader.construct_sequence(node)),
+ )
+
+ return loader
+
+
+def _get_class(path: str) -> type:
+ from importlib import import_module
+
+ module_path, _, class_name = path.rpartition(".")
+ mod = import_module(module_path)
+ try:
+ klass: type = getattr(mod, class_name)
+ except AttributeError:
+ raise ImportError(f"Class {class_name} is not in module {module_path}")
+ return klass
+
+
+def is_union_annotation(type_: Any) -> bool:
+ if sys.version_info >= (3, 10): # pragma: no cover
+ if isinstance(type_, types.UnionType):
+ return True
+ return getattr(type_, "__origin__", None) is Union
+
+
+def _resolve_optional(type_: Any) -> Tuple[bool, Any]:
+ """Check whether `type_` is equivalent to `typing.Optional[T]` for some T."""
+ if is_union_annotation(type_):
+ args = type_.__args__
+ if NoneType in args:
+ optional = True
+ args = tuple(a for a in args if a is not NoneType)
+ else:
+ optional = False
+ if len(args) == 1:
+ return optional, args[0]
+ elif len(args) >= 2:
+ return optional, Union[args]
+ else:
+ assert False
+
+ if type_ is Any:
+ return True, Any
+
+ if type_ in (None, NoneType):
+ return True, NoneType
+
+ return False, type_
+
+
+def _is_optional(obj: Any, key: Optional[Union[int, str]] = None) -> bool:
+ """Check `obj` metadata to see if the given node is optional."""
+ from .base import Container, Node
+
+ if key is not None:
+ assert isinstance(obj, Container)
+ obj = obj._get_node(key)
+ assert isinstance(obj, Node)
+ return obj._is_optional()
+
+
+def _resolve_forward(type_: Type[Any], module: str) -> Type[Any]:
+ import typing # lgtm [py/import-and-import-from]
+
+ forward = typing.ForwardRef if hasattr(typing, "ForwardRef") else typing._ForwardRef # type: ignore
+ if type(type_) is forward:
+ return _get_class(f"{module}.{type_.__forward_arg__}")
+ else:
+ if is_dict_annotation(type_):
+ kt, vt = get_dict_key_value_types(type_)
+ if kt is not None:
+ kt = _resolve_forward(kt, module=module)
+ if vt is not None:
+ vt = _resolve_forward(vt, module=module)
+ return Dict[kt, vt] # type: ignore
+ if is_list_annotation(type_):
+ et = get_list_element_type(type_)
+ if et is not None:
+ et = _resolve_forward(et, module=module)
+ return List[et] # type: ignore
+ if is_tuple_annotation(type_):
+ its = get_tuple_item_types(type_)
+ its = tuple(_resolve_forward(it, module=module) for it in its)
+ return Tuple[its] # type: ignore
+
+ return type_
+
+
+def extract_dict_subclass_data(obj: Any, parent: Any) -> Optional[Dict[str, Any]]:
+ """Check if obj is an instance of a subclass of Dict. If so, extract the Dict keys/values."""
+ from omegaconf.omegaconf import _maybe_wrap
+
+ is_type = isinstance(obj, type)
+ obj_type = obj if is_type else type(obj)
+ subclasses_dict = is_dict_subclass(obj_type)
+
+ if subclasses_dict:
+ warnings.warn(
+ f"Class `{obj_type.__name__}` subclasses `Dict`."
+ + " Subclassing `Dict` in Structured Config classes is deprecated,"
+ + " see github.com/omry/omegaconf/issues/663",
+ UserWarning,
+ stacklevel=9,
+ )
+
+ if is_type:
+ return None
+ elif subclasses_dict:
+ dict_subclass_data = {}
+ key_type, element_type = get_dict_key_value_types(obj_type)
+ for name, value in obj.items():
+ is_optional, type_ = _resolve_optional(element_type)
+ type_ = _resolve_forward(type_, obj.__module__)
+ try:
+ dict_subclass_data[name] = _maybe_wrap(
+ ref_type=type_,
+ is_optional=is_optional,
+ key=name,
+ value=value,
+ parent=parent,
+ )
+ except ValidationError as ex:
+ format_and_raise(
+ node=None, key=name, value=value, cause=ex, msg=str(ex)
+ )
+ return dict_subclass_data
+ else:
+ return None
+
+
+def get_attr_class_fields(obj: Any) -> List["attr.Attribute[Any]"]:
+ is_type = isinstance(obj, type)
+ obj_type = obj if is_type else type(obj)
+ fields = attr.fields_dict(obj_type).values()
+ return [f for f in fields if f.metadata.get("omegaconf_ignore") is not True]
+
+
+def get_attr_data(obj: Any, allow_objects: Optional[bool] = None) -> Dict[str, Any]:
+ from omegaconf.omegaconf import OmegaConf, _maybe_wrap
+
+ flags = {"allow_objects": allow_objects} if allow_objects is not None else {}
+
+ from omegaconf import MISSING
+
+ d = {}
+ is_type = isinstance(obj, type)
+ obj_type = obj if is_type else type(obj)
+ dummy_parent = OmegaConf.create({}, flags=flags)
+ dummy_parent._metadata.object_type = obj_type
+ resolved_hints = get_type_hints(obj_type)
+
+ for attrib in get_attr_class_fields(obj):
+ name = attrib.name
+ is_optional, type_ = _resolve_optional(resolved_hints[name])
+ type_ = _resolve_forward(type_, obj.__module__)
+ if not is_type:
+ value = getattr(obj, name)
+ else:
+ value = attrib.default
+ if value == attr.NOTHING:
+ value = MISSING
+ if is_union_annotation(type_) and not is_supported_union_annotation(type_):
+ e = ConfigValueError(
+ f"Unions of containers are not supported:\n{name}: {type_str(type_)}"
+ )
+ format_and_raise(node=None, key=None, value=value, cause=e, msg=str(e))
+
+ try:
+ d[name] = _maybe_wrap(
+ ref_type=type_,
+ is_optional=is_optional,
+ key=name,
+ value=value,
+ parent=dummy_parent,
+ )
+ except (ValidationError, GrammarParseError) as ex:
+ format_and_raise(
+ node=dummy_parent, key=name, value=value, cause=ex, msg=str(ex)
+ )
+ d[name]._set_parent(None)
+ dict_subclass_data = extract_dict_subclass_data(obj=obj, parent=dummy_parent)
+ if dict_subclass_data is not None:
+ d.update(dict_subclass_data)
+ return d
+
+
+def get_dataclass_fields(obj: Any) -> List["dataclasses.Field[Any]"]:
+ fields = dataclasses.fields(obj)
+ return [f for f in fields if f.metadata.get("omegaconf_ignore") is not True]
+
+
+def get_dataclass_data(
+ obj: Any, allow_objects: Optional[bool] = None
+) -> Dict[str, Any]:
+ from omegaconf.omegaconf import MISSING, OmegaConf, _maybe_wrap
+
+ flags = {"allow_objects": allow_objects} if allow_objects is not None else {}
+ d = {}
+ is_type = isinstance(obj, type)
+ obj_type = get_type_of(obj)
+ dummy_parent = OmegaConf.create({}, flags=flags)
+ dummy_parent._metadata.object_type = obj_type
+ resolved_hints = get_type_hints(obj_type)
+ for field in get_dataclass_fields(obj):
+ name = field.name
+ is_optional, type_ = _resolve_optional(resolved_hints[field.name])
+ type_ = _resolve_forward(type_, obj.__module__)
+ has_default = field.default != dataclasses.MISSING
+ has_default_factory = field.default_factory != dataclasses.MISSING
+
+ if not is_type:
+ value = getattr(obj, name)
+ else:
+ if has_default:
+ value = field.default
+ elif has_default_factory:
+ value = field.default_factory() # type: ignore
+ else:
+ value = MISSING
+
+ if is_union_annotation(type_) and not is_supported_union_annotation(type_):
+ e = ConfigValueError(
+ f"Unions of containers are not supported:\n{name}: {type_str(type_)}"
+ )
+ format_and_raise(node=None, key=None, value=value, cause=e, msg=str(e))
+ try:
+ d[name] = _maybe_wrap(
+ ref_type=type_,
+ is_optional=is_optional,
+ key=name,
+ value=value,
+ parent=dummy_parent,
+ )
+ except (ValidationError, GrammarParseError) as ex:
+ format_and_raise(
+ node=dummy_parent, key=name, value=value, cause=ex, msg=str(ex)
+ )
+ d[name]._set_parent(None)
+ dict_subclass_data = extract_dict_subclass_data(obj=obj, parent=dummy_parent)
+ if dict_subclass_data is not None:
+ d.update(dict_subclass_data)
+ return d
+
+
+def is_dataclass(obj: Any) -> bool:
+ from omegaconf.base import Node
+
+ if dataclasses is None or isinstance(obj, Node):
+ return False
+ return dataclasses.is_dataclass(obj)
+
+
+def is_attr_class(obj: Any) -> bool:
+ from omegaconf.base import Node
+
+ if attr is None or isinstance(obj, Node):
+ return False
+ return attr.has(obj)
+
+
+def is_structured_config(obj: Any) -> bool:
+ return is_attr_class(obj) or is_dataclass(obj)
+
+
+def is_dataclass_frozen(type_: Any) -> bool:
+ return type_.__dataclass_params__.frozen # type: ignore
+
+
+def is_attr_frozen(type_: type) -> bool:
+ # This is very hacky and probably fragile as well.
+ # Unfortunately currently there isn't an official API in attr that can detect that.
+ # noinspection PyProtectedMember
+ return type_.__setattr__ == attr._make._frozen_setattrs # type: ignore
+
+
+def get_type_of(class_or_object: Any) -> Type[Any]:
+ type_ = class_or_object
+ if not isinstance(type_, type):
+ type_ = type(class_or_object)
+ assert isinstance(type_, type)
+ return type_
+
+
+def is_structured_config_frozen(obj: Any) -> bool:
+ type_ = get_type_of(obj)
+
+ if is_dataclass(type_):
+ return is_dataclass_frozen(type_)
+ if is_attr_class(type_):
+ return is_attr_frozen(type_)
+ return False
+
+
+def get_structured_config_init_field_names(obj: Any) -> List[str]:
+ fields: Union[List["dataclasses.Field[Any]"], List["attr.Attribute[Any]"]]
+ if is_dataclass(obj):
+ fields = get_dataclass_fields(obj)
+ elif is_attr_class(obj):
+ fields = get_attr_class_fields(obj)
+ else:
+ raise ValueError(f"Unsupported type: {type(obj).__name__}")
+ return [f.name for f in fields if f.init]
+
+
+def get_structured_config_data(
+ obj: Any, allow_objects: Optional[bool] = None
+) -> Dict[str, Any]:
+ if is_dataclass(obj):
+ return get_dataclass_data(obj, allow_objects=allow_objects)
+ elif is_attr_class(obj):
+ return get_attr_data(obj, allow_objects=allow_objects)
+ else:
+ raise ValueError(f"Unsupported type: {type(obj).__name__}")
+
+
+class ValueKind(Enum):
+ VALUE = 0
+ MANDATORY_MISSING = 1
+ INTERPOLATION = 2
+
+
+def _is_missing_value(value: Any) -> bool:
+ from omegaconf import Node
+
+ if isinstance(value, Node):
+ value = value._value()
+ return _is_missing_literal(value)
+
+
+def _is_missing_literal(value: Any) -> bool:
+ # Uses literal '???' instead of the MISSING const for performance reasons.
+ return isinstance(value, str) and value == "???"
+
+
+def _is_none(
+ value: Any, resolve: bool = False, throw_on_resolution_failure: bool = True
+) -> bool:
+ from omegaconf import Node
+
+ if not isinstance(value, Node):
+ return value is None
+
+ if resolve:
+ value = value._maybe_dereference_node(
+ throw_on_resolution_failure=throw_on_resolution_failure
+ )
+ if not throw_on_resolution_failure and value is None:
+ # Resolution failure: consider that it is *not* None.
+ return False
+ assert isinstance(value, Node)
+
+ return value._is_none()
+
+
+def get_value_kind(
+ value: Any, strict_interpolation_validation: bool = False
+) -> ValueKind:
+ """
+ Determine the kind of a value
+ Examples:
+ VALUE: "10", "20", True
+ MANDATORY_MISSING: "???"
+ INTERPOLATION: "${foo.bar}", "${foo.${bar}}", "${foo:bar}", "[${foo}, ${bar}]",
+ "ftp://${host}/path", "${foo:${bar}, [true], {'baz': ${baz}}}"
+
+ :param value: Input to classify.
+ :param strict_interpolation_validation: If `True`, then when `value` is a string
+ containing "${", it is parsed to validate the interpolation syntax. If `False`,
+ this parsing step is skipped: this is more efficient, but will not detect errors.
+ """
+
+ if _is_missing_value(value):
+ return ValueKind.MANDATORY_MISSING
+
+ if _is_interpolation(value, strict_interpolation_validation):
+ return ValueKind.INTERPOLATION
+
+ return ValueKind.VALUE
+
+
+def _is_interpolation(v: Any, strict_interpolation_validation: bool = False) -> bool:
+ from omegaconf import Node
+
+ if isinstance(v, Node):
+ v = v._value()
+
+ if isinstance(v, str) and _is_interpolation_string(
+ v, strict_interpolation_validation
+ ):
+ return True
+ return False
+
+
+def _is_interpolation_string(value: str, strict_interpolation_validation: bool) -> bool:
+ # We identify potential interpolations by the presence of "${" in the string.
+ # Note that escaped interpolations (ex: "esc: \${bar}") are identified as
+ # interpolations: this is intended, since they must be processed as interpolations
+ # for the string to be properly un-escaped.
+ # Keep in mind that invalid interpolations will only be detected when
+ # `strict_interpolation_validation` is True.
+ if "${" in value:
+ if strict_interpolation_validation:
+ # First try the cheap regex matching that detects common interpolations.
+ if SIMPLE_INTERPOLATION_PATTERN.match(value) is None:
+ # If no match, do the more expensive grammar parsing to detect errors.
+ parse(value)
+ return True
+ return False
+
+
+def _is_special(value: Any) -> bool:
+ """Special values are None, MISSING, and interpolation."""
+ return _is_none(value) or get_value_kind(value) in (
+ ValueKind.MANDATORY_MISSING,
+ ValueKind.INTERPOLATION,
+ )
+
+
+def is_float(st: str) -> bool:
+ try:
+ float(st)
+ return True
+ except ValueError:
+ return False
+
+
+def is_int(st: str) -> bool:
+ try:
+ int(st)
+ return True
+ except ValueError:
+ return False
+
+
+def is_primitive_list(obj: Any) -> bool:
+ return isinstance(obj, (list, tuple))
+
+
+def is_primitive_dict(obj: Any) -> bool:
+ t = get_type_of(obj)
+ return t is dict
+
+
+def is_dict_annotation(type_: Any) -> bool:
+ if type_ in (dict, Dict):
+ return True
+ origin = getattr(type_, "__origin__", None)
+ # type_dict is a bit hard to detect.
+ # this support is tentative, if it eventually causes issues in other areas it may be dropped.
+ if sys.version_info < (3, 7, 0): # pragma: no cover
+ typed_dict = hasattr(type_, "__base__") and type_.__base__ == Dict
+ return origin is Dict or type_ is Dict or typed_dict
+ else: # pragma: no cover
+ typed_dict = hasattr(type_, "__base__") and type_.__base__ == dict
+ return origin is dict or typed_dict
+
+
+def is_list_annotation(type_: Any) -> bool:
+ if type_ in (list, List):
+ return True
+ origin = getattr(type_, "__origin__", None)
+ if sys.version_info < (3, 7, 0):
+ return origin is List or type_ is List # pragma: no cover
+ else:
+ return origin is list # pragma: no cover
+
+
+def is_tuple_annotation(type_: Any) -> bool:
+ if type_ in (tuple, Tuple):
+ return True
+ origin = getattr(type_, "__origin__", None)
+ if sys.version_info < (3, 7, 0):
+ return origin is Tuple or type_ is Tuple # pragma: no cover
+ else:
+ return origin is tuple # pragma: no cover
+
+
+def is_supported_union_annotation(obj: Any) -> bool:
+ """Currently only primitive types are supported in Unions, e.g. Union[int, str]"""
+ if not is_union_annotation(obj):
+ return False
+ args = obj.__args__
+ return all(is_primitive_type_annotation(arg) for arg in args)
+
+
+def is_dict_subclass(type_: Any) -> bool:
+ return type_ is not None and isinstance(type_, type) and issubclass(type_, Dict)
+
+
+def is_dict(obj: Any) -> bool:
+ return is_primitive_dict(obj) or is_dict_annotation(obj) or is_dict_subclass(obj)
+
+
+def is_primitive_container(obj: Any) -> bool:
+ return is_primitive_list(obj) or is_primitive_dict(obj)
+
+
+def get_list_element_type(ref_type: Optional[Type[Any]]) -> Any:
+ args = getattr(ref_type, "__args__", None)
+ if ref_type is not List and args is not None and args[0]:
+ element_type = args[0]
+ else:
+ element_type = Any
+ return element_type
+
+
+def get_tuple_item_types(ref_type: Type[Any]) -> Tuple[Any, ...]:
+ args = getattr(ref_type, "__args__", None)
+ if args in (None, ()):
+ args = (Any, ...)
+ assert isinstance(args, tuple)
+ return args
+
+
+def get_dict_key_value_types(ref_type: Any) -> Tuple[Any, Any]:
+ args = getattr(ref_type, "__args__", None)
+ if args is None:
+ bases = getattr(ref_type, "__orig_bases__", None)
+ if bases is not None and len(bases) > 0:
+ args = getattr(bases[0], "__args__", None)
+
+ key_type: Any
+ element_type: Any
+ if ref_type is None or ref_type == Dict:
+ key_type = Any
+ element_type = Any
+ else:
+ if args is not None:
+ key_type = args[0]
+ element_type = args[1]
+ else:
+ key_type = Any
+ element_type = Any
+
+ return key_type, element_type
+
+
+def is_valid_value_annotation(type_: Any) -> bool:
+ _, type_ = _resolve_optional(type_)
+ return (
+ type_ is Any
+ or is_primitive_type_annotation(type_)
+ or is_structured_config(type_)
+ or is_container_annotation(type_)
+ or is_supported_union_annotation(type_)
+ )
+
+
+def _valid_dict_key_annotation_type(type_: Any) -> bool:
+ from omegaconf import DictKeyType
+
+ return type_ is None or type_ is Any or issubclass(type_, DictKeyType.__args__) # type: ignore
+
+
+def is_primitive_type_annotation(type_: Any) -> bool:
+ type_ = get_type_of(type_)
+ return issubclass(type_, (Enum, pathlib.Path)) or type_ in BUILTIN_VALUE_TYPES
+
+
+def _get_value(value: Any) -> Any:
+ from .base import Container, UnionNode
+ from .nodes import ValueNode
+
+ if isinstance(value, ValueNode):
+ return value._value()
+ elif isinstance(value, Container):
+ boxed = value._value()
+ if boxed is None or _is_missing_literal(boxed) or _is_interpolation(boxed):
+ return boxed
+ elif isinstance(value, UnionNode):
+ boxed = value._value()
+ if boxed is None or _is_missing_literal(boxed) or _is_interpolation(boxed):
+ return boxed
+ else:
+ return _get_value(boxed) # pass through value of boxed node
+
+ # return primitives and regular OmegaConf Containers as is
+ return value
+
+
+def get_type_hint(obj: Any, key: Any = None) -> Optional[Type[Any]]:
+ from omegaconf import Container, Node
+
+ if isinstance(obj, Container):
+ if key is not None:
+ obj = obj._get_node(key)
+ else:
+ if key is not None:
+ raise ValueError("Key must only be provided when obj is a container")
+
+ if isinstance(obj, Node):
+ ref_type = obj._metadata.ref_type
+ if obj._is_optional() and ref_type is not Any:
+ return Optional[ref_type] # type: ignore
+ else:
+ return ref_type
+ else:
+ return Any # type: ignore
+
+
+def _raise(ex: Exception, cause: Exception) -> None:
+ # Set the environment variable OC_CAUSE=1 to get a stacktrace that includes the
+ # causing exception.
+ env_var = os.environ["OC_CAUSE"] if "OC_CAUSE" in os.environ else None
+ debugging = sys.gettrace() is not None
+ full_backtrace = (debugging and not env_var == "0") or (env_var == "1")
+ if full_backtrace:
+ ex.__cause__ = cause
+ else:
+ ex.__cause__ = None
+ raise ex.with_traceback(sys.exc_info()[2]) # set env var OC_CAUSE=1 for full trace
+
+
+def format_and_raise(
+ node: Any,
+ key: Any,
+ value: Any,
+ msg: str,
+ cause: Exception,
+ type_override: Any = None,
+) -> None:
+ from omegaconf import OmegaConf
+ from omegaconf.base import Node
+
+ if isinstance(cause, AssertionError):
+ raise
+
+ if isinstance(cause, OmegaConfBaseException) and cause._initialized:
+ ex = cause
+ if type_override is not None:
+ ex = type_override(str(cause))
+ ex.__dict__ = copy.deepcopy(cause.__dict__)
+ _raise(ex, cause)
+
+ object_type: Optional[Type[Any]]
+ object_type_str: Optional[str] = None
+ ref_type: Optional[Type[Any]]
+ ref_type_str: Optional[str]
+
+ child_node: Optional[Node] = None
+ if node is None:
+ full_key = key if key is not None else ""
+ object_type = None
+ ref_type = None
+ ref_type_str = None
+ else:
+ if key is not None and not node._is_none():
+ child_node = node._get_node(key, validate_access=False)
+
+ try:
+ full_key = node._get_full_key(key=key)
+ except Exception as exc:
+ # Since we are handling an exception, raising a different one here would
+ # be misleading. Instead, we display it in the key.
+ full_key = f""
+
+ object_type = OmegaConf.get_type(node)
+ object_type_str = type_str(object_type)
+
+ ref_type = get_type_hint(node)
+ ref_type_str = type_str(ref_type)
+
+ msg = string.Template(msg).safe_substitute(
+ REF_TYPE=ref_type_str,
+ OBJECT_TYPE=object_type_str,
+ KEY=key,
+ FULL_KEY=full_key,
+ VALUE=value,
+ VALUE_TYPE=type_str(type(value), include_module_name=True),
+ KEY_TYPE=f"{type(key).__name__}",
+ )
+
+ if ref_type not in (None, Any):
+ template = dedent(
+ """\
+ $MSG
+ full_key: $FULL_KEY
+ reference_type=$REF_TYPE
+ object_type=$OBJECT_TYPE"""
+ )
+ else:
+ template = dedent(
+ """\
+ $MSG
+ full_key: $FULL_KEY
+ object_type=$OBJECT_TYPE"""
+ )
+ s = string.Template(template=template)
+
+ message = s.substitute(
+ REF_TYPE=ref_type_str, OBJECT_TYPE=object_type_str, MSG=msg, FULL_KEY=full_key
+ )
+ exception_type = type(cause) if type_override is None else type_override
+ if exception_type == TypeError:
+ exception_type = ConfigTypeError
+ elif exception_type == IndexError:
+ exception_type = ConfigIndexError
+
+ ex = exception_type(f"{message}")
+ if issubclass(exception_type, OmegaConfBaseException):
+ ex._initialized = True
+ ex.msg = message
+ ex.parent_node = node
+ ex.child_node = child_node
+ ex.key = key
+ ex.full_key = full_key
+ ex.value = value
+ ex.object_type = object_type
+ ex.object_type_str = object_type_str
+ ex.ref_type = ref_type
+ ex.ref_type_str = ref_type_str
+
+ _raise(ex, cause)
+
+
+def type_str(t: Any, include_module_name: bool = False) -> str:
+ is_optional, t = _resolve_optional(t)
+ if t is NoneType:
+ return str(t.__name__)
+ if t is Any:
+ return "Any"
+ if t is ...:
+ return "..."
+
+ if hasattr(t, "__name__"):
+ name = str(t.__name__)
+ elif getattr(t, "_name", None) is not None: # pragma: no cover
+ name = str(t._name)
+ elif getattr(t, "__origin__", None) is not None: # pragma: no cover
+ name = type_str(t.__origin__)
+ else:
+ name = str(t)
+ if name.startswith("typing."): # pragma: no cover
+ name = name[len("typing.") :]
+
+ args = getattr(t, "__args__", None)
+ if args is not None:
+ args = ", ".join(
+ [type_str(t, include_module_name=include_module_name) for t in t.__args__]
+ )
+ ret = f"{name}[{args}]"
+ else:
+ ret = name
+ if include_module_name:
+ if (
+ hasattr(t, "__module__")
+ and t.__module__ != "builtins"
+ and t.__module__ != "typing"
+ and not t.__module__.startswith("omegaconf.")
+ ):
+ module_prefix = str(t.__module__) + "."
+ else:
+ module_prefix = ""
+ ret = module_prefix + ret
+ if is_optional:
+ return f"Optional[{ret}]"
+ else:
+ return ret
+
+
+def _ensure_container(target: Any, flags: Optional[Dict[str, bool]] = None) -> Any:
+ from omegaconf import OmegaConf
+
+ if is_primitive_container(target):
+ assert isinstance(target, (list, dict))
+ target = OmegaConf.create(target, flags=flags)
+ elif is_structured_config(target):
+ target = OmegaConf.structured(target, flags=flags)
+ elif not OmegaConf.is_config(target):
+ raise ValueError(
+ "Invalid input. Supports one of "
+ + "[dict,list,DictConfig,ListConfig,dataclass,dataclass instance,attr class,attr class instance]"
+ )
+
+ return target
+
+
+def is_generic_list(type_: Any) -> bool:
+ """
+ Checks if a type is a generic list, for example:
+ list returns False
+ typing.List returns False
+ typing.List[T] returns True
+
+ :param type_: variable type
+ :return: bool
+ """
+ return is_list_annotation(type_) and get_list_element_type(type_) is not None
+
+
+def is_generic_dict(type_: Any) -> bool:
+ """
+ Checks if a type is a generic dict, for example:
+ list returns False
+ typing.List returns False
+ typing.List[T] returns True
+
+ :param type_: variable type
+ :return: bool
+ """
+ return is_dict_annotation(type_) and len(get_dict_key_value_types(type_)) > 0
+
+
+def is_container_annotation(type_: Any) -> bool:
+ return is_list_annotation(type_) or is_dict_annotation(type_)
+
+
+def split_key(key: str) -> List[str]:
+ """
+ Split a full key path into its individual components.
+
+ This is similar to `key.split(".")` but also works with the getitem syntax:
+ "a.b" -> ["a", "b"]
+ "a[b]" -> ["a", "b"]
+ ".a.b[c].d" -> ["", "a", "b", "c", "d"]
+ "[a].b" -> ["a", "b"]
+ """
+ # Obtain the first part of the key (in docstring examples: a, a, .a, '')
+ first = KEY_PATH_HEAD.match(key)
+ assert first is not None
+ first_stop = first.span()[1]
+
+ # `tokens` will contain all elements composing the key.
+ tokens = key[0:first_stop].split(".")
+
+ # Optimization in case `key` has no other component: we are done.
+ if first_stop == len(key):
+ return tokens
+
+ if key[first_stop] == "[" and not tokens[-1]:
+ # This is a special case where the first key starts with brackets, e.g.
+ # [a] or ..[a]. In that case there is an extra "" in `tokens` that we
+ # need to get rid of:
+ # [a] -> tokens = [""] but we would like []
+ # ..[a] -> tokens = ["", "", ""] but we would like ["", ""]
+ tokens.pop()
+
+ # Identify other key elements (in docstring examples: b, b, b/c/d, b)
+ others = KEY_PATH_OTHER.findall(key[first_stop:])
+
+ # There are two groups in the `KEY_PATH_OTHER` regex: one for keys starting
+ # with a dot (.b, .d) and one for keys starting with a bracket ([b], [c]).
+ # Only one group can be non-empty.
+ tokens += [dot_key if dot_key else bracket_key for dot_key, bracket_key in others]
+
+ return tokens
+
+
+# Similar to Python 3.7+'s `contextlib.nullcontext` (which should be used instead,
+# once support for Python 3.6 is dropped).
+@contextmanager
+def nullcontext(enter_result: Any = None) -> Iterator[Any]:
+ yield enter_result
diff --git a/cameras/deps/omegaconf/base.py b/cameras/deps/omegaconf/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b013d1d9fac789cc28e8ea0a23e586610a32f43
--- /dev/null
+++ b/cameras/deps/omegaconf/base.py
@@ -0,0 +1,962 @@
+import copy
+import sys
+from abc import ABC, abstractmethod
+from collections import defaultdict
+from dataclasses import dataclass, field
+from enum import Enum
+from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type, Union
+
+from antlr4 import ParserRuleContext
+
+from ._utils import (
+ _DEFAULT_MARKER_,
+ NoneType,
+ ValueKind,
+ _get_value,
+ _is_interpolation,
+ _is_missing_value,
+ _is_special,
+ format_and_raise,
+ get_value_kind,
+ is_union_annotation,
+ is_valid_value_annotation,
+ split_key,
+ type_str,
+)
+from .errors import (
+ ConfigKeyError,
+ ConfigTypeError,
+ InterpolationKeyError,
+ InterpolationResolutionError,
+ InterpolationToMissingValueError,
+ InterpolationValidationError,
+ MissingMandatoryValue,
+ UnsupportedInterpolationType,
+ ValidationError,
+)
+from .grammar.gen.OmegaConfGrammarParser import OmegaConfGrammarParser
+from .grammar_parser import parse
+from .grammar_visitor import GrammarVisitor
+
+DictKeyType = Union[str, bytes, int, Enum, float, bool]
+
+
+@dataclass
+class Metadata:
+
+ ref_type: Union[Type[Any], Any]
+
+ object_type: Union[Type[Any], Any]
+
+ optional: bool
+
+ key: Any
+
+ # Flags have 3 modes:
+ # unset : inherit from parent (None if no parent specifies)
+ # set to true: flag is true
+ # set to false: flag is false
+ flags: Optional[Dict[str, bool]] = None
+
+ # If True, when checking the value of a flag, if the flag is not set None is returned
+ # otherwise, the parent node is queried.
+ flags_root: bool = False
+
+ resolver_cache: Dict[str, Any] = field(default_factory=lambda: defaultdict(dict))
+
+ def __post_init__(self) -> None:
+ if self.flags is None:
+ self.flags = {}
+
+ @property
+ def type_hint(self) -> Union[Type[Any], Any]:
+ """Compute `type_hint` from `self.optional` and `self.ref_type`"""
+ # For compatibility with pickled OmegaConf objects created using older
+ # versions of OmegaConf, we store `ref_type` and `object_type`
+ # separately (rather than storing `type_hint` directly).
+ if self.optional:
+ return Optional[self.ref_type]
+ else:
+ return self.ref_type
+
+
+@dataclass
+class ContainerMetadata(Metadata):
+ key_type: Any = None
+ element_type: Any = None
+
+ def __post_init__(self) -> None:
+ if self.ref_type is None:
+ self.ref_type = Any
+ assert self.key_type is Any or isinstance(self.key_type, type)
+ if self.element_type is not None:
+ if not is_valid_value_annotation(self.element_type):
+ raise ValidationError(
+ f"Unsupported value type: '{type_str(self.element_type, include_module_name=True)}'"
+ )
+
+ if self.flags is None:
+ self.flags = {}
+
+
+class Node(ABC):
+ _metadata: Metadata
+
+ _parent: Optional["Box"]
+ _flags_cache: Optional[Dict[str, Optional[bool]]]
+
+ def __init__(self, parent: Optional["Box"], metadata: Metadata):
+ self.__dict__["_metadata"] = metadata
+ self.__dict__["_parent"] = parent
+ self.__dict__["_flags_cache"] = None
+
+ def __getstate__(self) -> Dict[str, Any]:
+ # Overridden to ensure that the flags cache is cleared on serialization.
+ state_dict = copy.copy(self.__dict__)
+ del state_dict["_flags_cache"]
+ return state_dict
+
+ def __setstate__(self, state_dict: Dict[str, Any]) -> None:
+ self.__dict__.update(state_dict)
+ self.__dict__["_flags_cache"] = None
+
+ def _set_parent(self, parent: Optional["Box"]) -> None:
+ assert parent is None or isinstance(parent, Box)
+ self.__dict__["_parent"] = parent
+ self._invalidate_flags_cache()
+
+ def _invalidate_flags_cache(self) -> None:
+ self.__dict__["_flags_cache"] = None
+
+ def _get_parent(self) -> Optional["Box"]:
+ parent = self.__dict__["_parent"]
+ assert parent is None or isinstance(parent, Box)
+ return parent
+
+ def _get_parent_container(self) -> Optional["Container"]:
+ """
+ Like _get_parent, but returns the grandparent
+ in the case where `self` is wrapped by a UnionNode.
+ """
+ parent = self.__dict__["_parent"]
+ assert parent is None or isinstance(parent, Box)
+
+ if isinstance(parent, UnionNode):
+ grandparent = parent.__dict__["_parent"]
+ assert grandparent is None or isinstance(grandparent, Container)
+ return grandparent
+ else:
+ assert parent is None or isinstance(parent, Container)
+ return parent
+
+ def _set_flag(
+ self,
+ flags: Union[List[str], str],
+ values: Union[List[Optional[bool]], Optional[bool]],
+ ) -> "Node":
+ if isinstance(flags, str):
+ flags = [flags]
+
+ if values is None or isinstance(values, bool):
+ values = [values]
+
+ if len(values) == 1:
+ values = len(flags) * values
+
+ if len(flags) != len(values):
+ raise ValueError("Inconsistent lengths of input flag names and values")
+
+ for idx, flag in enumerate(flags):
+ value = values[idx]
+ if value is None:
+ assert self._metadata.flags is not None
+ if flag in self._metadata.flags:
+ del self._metadata.flags[flag]
+ else:
+ assert self._metadata.flags is not None
+ self._metadata.flags[flag] = value
+ self._invalidate_flags_cache()
+ return self
+
+ def _get_node_flag(self, flag: str) -> Optional[bool]:
+ """
+ :param flag: flag to inspect
+ :return: the state of the flag on this node.
+ """
+ assert self._metadata.flags is not None
+ return self._metadata.flags.get(flag)
+
+ def _get_flag(self, flag: str) -> Optional[bool]:
+ cache = self.__dict__["_flags_cache"]
+ if cache is None:
+ cache = self.__dict__["_flags_cache"] = {}
+
+ ret = cache.get(flag, _DEFAULT_MARKER_)
+ if ret is _DEFAULT_MARKER_:
+ ret = self._get_flag_no_cache(flag)
+ cache[flag] = ret
+ assert ret is None or isinstance(ret, bool)
+ return ret
+
+ def _get_flag_no_cache(self, flag: str) -> Optional[bool]:
+ """
+ Returns True if this config node flag is set
+ A flag is set if node.set_flag(True) was called
+ or one if it's parents is flag is set
+ :return:
+ """
+ flags = self._metadata.flags
+ assert flags is not None
+ if flag in flags and flags[flag] is not None:
+ return flags[flag]
+
+ if self._is_flags_root():
+ return None
+
+ parent = self._get_parent()
+ if parent is None:
+ return None
+ else:
+ # noinspection PyProtectedMember
+ return parent._get_flag(flag)
+
+ def _format_and_raise(
+ self,
+ key: Any,
+ value: Any,
+ cause: Exception,
+ msg: Optional[str] = None,
+ type_override: Any = None,
+ ) -> None:
+ format_and_raise(
+ node=self,
+ key=key,
+ value=value,
+ msg=str(cause) if msg is None else msg,
+ cause=cause,
+ type_override=type_override,
+ )
+ assert False
+
+ @abstractmethod
+ def _get_full_key(self, key: Optional[Union[DictKeyType, int]]) -> str:
+ ...
+
+ def _dereference_node(self) -> "Node":
+ node = self._dereference_node_impl(throw_on_resolution_failure=True)
+ assert node is not None
+ return node
+
+ def _maybe_dereference_node(
+ self,
+ throw_on_resolution_failure: bool = False,
+ memo: Optional[Set[int]] = None,
+ ) -> Optional["Node"]:
+ return self._dereference_node_impl(
+ throw_on_resolution_failure=throw_on_resolution_failure,
+ memo=memo,
+ )
+
+ def _dereference_node_impl(
+ self,
+ throw_on_resolution_failure: bool,
+ memo: Optional[Set[int]] = None,
+ ) -> Optional["Node"]:
+ if not self._is_interpolation():
+ return self
+
+ parent = self._get_parent_container()
+ if parent is None:
+ if throw_on_resolution_failure:
+ raise InterpolationResolutionError(
+ "Cannot resolve interpolation for a node without a parent"
+ )
+ return None
+ assert parent is not None
+ key = self._key()
+ return parent._resolve_interpolation_from_parse_tree(
+ parent=parent,
+ key=key,
+ value=self,
+ parse_tree=parse(_get_value(self)),
+ throw_on_resolution_failure=throw_on_resolution_failure,
+ memo=memo,
+ )
+
+ def _get_root(self) -> "Container":
+ root: Optional[Box] = self._get_parent()
+ if root is None:
+ assert isinstance(self, Container)
+ return self
+ assert root is not None and isinstance(root, Box)
+ while root._get_parent() is not None:
+ root = root._get_parent()
+ assert root is not None and isinstance(root, Box)
+ assert root is not None and isinstance(root, Container)
+ return root
+
+ def _is_missing(self) -> bool:
+ """
+ Check if the node's value is `???` (does *not* resolve interpolations).
+ """
+ return _is_missing_value(self)
+
+ def _is_none(self) -> bool:
+ """
+ Check if the node's value is `None` (does *not* resolve interpolations).
+ """
+ return self._value() is None
+
+ @abstractmethod
+ def __eq__(self, other: Any) -> bool:
+ ...
+
+ @abstractmethod
+ def __ne__(self, other: Any) -> bool:
+ ...
+
+ @abstractmethod
+ def __hash__(self) -> int:
+ ...
+
+ @abstractmethod
+ def _value(self) -> Any:
+ ...
+
+ @abstractmethod
+ def _set_value(self, value: Any, flags: Optional[Dict[str, bool]] = None) -> None:
+ ...
+
+ @abstractmethod
+ def _is_optional(self) -> bool:
+ ...
+
+ @abstractmethod
+ def _is_interpolation(self) -> bool:
+ ...
+
+ def _key(self) -> Any:
+ return self._metadata.key
+
+ def _set_key(self, key: Any) -> None:
+ self._metadata.key = key
+
+ def _is_flags_root(self) -> bool:
+ return self._metadata.flags_root
+
+ def _set_flags_root(self, flags_root: bool) -> None:
+ if self._metadata.flags_root != flags_root:
+ self._metadata.flags_root = flags_root
+ self._invalidate_flags_cache()
+
+ def _has_ref_type(self) -> bool:
+ return self._metadata.ref_type is not Any
+
+
+class Box(Node):
+ """
+ Base class for nodes that can contain other nodes.
+ Concrete subclasses include DictConfig, ListConfig, and UnionNode.
+ """
+
+ _content: Any
+
+ def __init__(self, parent: Optional["Box"], metadata: Metadata):
+ super().__init__(parent=parent, metadata=metadata)
+ self.__dict__["_content"] = None
+
+ def __copy__(self) -> Any:
+ # real shallow copy is impossible because of the reference to the parent.
+ return copy.deepcopy(self)
+
+ def _re_parent(self) -> None:
+ from .dictconfig import DictConfig
+ from .listconfig import ListConfig
+
+ # update parents of first level Config nodes to self
+
+ if isinstance(self, DictConfig):
+ content = self.__dict__["_content"]
+ if isinstance(content, dict):
+ for _key, value in self.__dict__["_content"].items():
+ if value is not None:
+ value._set_parent(self)
+ if isinstance(value, Box):
+ value._re_parent()
+ elif isinstance(self, ListConfig):
+ content = self.__dict__["_content"]
+ if isinstance(content, list):
+ for item in self.__dict__["_content"]:
+ if item is not None:
+ item._set_parent(self)
+ if isinstance(item, Box):
+ item._re_parent()
+ elif isinstance(self, UnionNode):
+ content = self.__dict__["_content"]
+ if isinstance(content, Node):
+ content._set_parent(self)
+ if isinstance(content, Box): # pragma: no cover
+ # No coverage here as support for containers inside
+ # UnionNode is not yet implemented
+ content._re_parent()
+
+
+class Container(Box):
+ """
+ Container tagging interface
+ """
+
+ _metadata: ContainerMetadata
+
+ @abstractmethod
+ def _get_child(
+ self,
+ key: Any,
+ validate_access: bool = True,
+ validate_key: bool = True,
+ throw_on_missing_value: bool = False,
+ throw_on_missing_key: bool = False,
+ ) -> Union[Optional[Node], List[Optional[Node]]]:
+ ...
+
+ @abstractmethod
+ def _get_node(
+ self,
+ key: Any,
+ validate_access: bool = True,
+ validate_key: bool = True,
+ throw_on_missing_value: bool = False,
+ throw_on_missing_key: bool = False,
+ ) -> Union[Optional[Node], List[Optional[Node]]]:
+ ...
+
+ @abstractmethod
+ def __delitem__(self, key: Any) -> None:
+ ...
+
+ @abstractmethod
+ def __setitem__(self, key: Any, value: Any) -> None:
+ ...
+
+ @abstractmethod
+ def __iter__(self) -> Iterator[Any]:
+ ...
+
+ @abstractmethod
+ def __getitem__(self, key_or_index: Any) -> Any:
+ ...
+
+ def _resolve_key_and_root(self, key: str) -> Tuple["Container", str]:
+ orig = key
+ if not key.startswith("."):
+ return self._get_root(), key
+ else:
+ root: Optional[Container] = self
+ assert key.startswith(".")
+ while True:
+ assert root is not None
+ key = key[1:]
+ if not key.startswith("."):
+ break
+ root = root._get_parent_container()
+ if root is None:
+ raise ConfigKeyError(f"Error resolving key '{orig}'")
+
+ return root, key
+
+ def _select_impl(
+ self,
+ key: str,
+ throw_on_missing: bool,
+ throw_on_resolution_failure: bool,
+ memo: Optional[Set[int]] = None,
+ ) -> Tuple[Optional["Container"], Optional[str], Optional[Node]]:
+ """
+ Select a value using dot separated key sequence
+ """
+ from .omegaconf import _select_one
+
+ if key == "":
+ return self, "", self
+
+ split = split_key(key)
+ root: Optional[Container] = self
+ for i in range(len(split) - 1):
+ if root is None:
+ break
+
+ k = split[i]
+ ret, _ = _select_one(
+ c=root,
+ key=k,
+ throw_on_missing=throw_on_missing,
+ throw_on_type_error=throw_on_resolution_failure,
+ )
+ if isinstance(ret, Node):
+ ret = ret._maybe_dereference_node(
+ throw_on_resolution_failure=throw_on_resolution_failure,
+ memo=memo,
+ )
+
+ if ret is not None and not isinstance(ret, Container):
+ parent_key = ".".join(split[0 : i + 1])
+ child_key = split[i + 1]
+ raise ConfigTypeError(
+ f"Error trying to access {key}: node `{parent_key}` "
+ f"is not a container and thus cannot contain `{child_key}`"
+ )
+ root = ret
+
+ if root is None:
+ return None, None, None
+
+ last_key = split[-1]
+ value, _ = _select_one(
+ c=root,
+ key=last_key,
+ throw_on_missing=throw_on_missing,
+ throw_on_type_error=throw_on_resolution_failure,
+ )
+ if value is None:
+ return root, last_key, None
+
+ if memo is not None:
+ vid = id(value)
+ if vid in memo:
+ raise InterpolationResolutionError("Recursive interpolation detected")
+ # push to memo "stack"
+ memo.add(vid)
+
+ try:
+ value = root._maybe_resolve_interpolation(
+ parent=root,
+ key=last_key,
+ value=value,
+ throw_on_resolution_failure=throw_on_resolution_failure,
+ memo=memo,
+ )
+ finally:
+ if memo is not None:
+ # pop from memo "stack"
+ memo.remove(vid)
+
+ return root, last_key, value
+
+ def _resolve_interpolation_from_parse_tree(
+ self,
+ parent: Optional["Container"],
+ value: "Node",
+ key: Any,
+ parse_tree: OmegaConfGrammarParser.ConfigValueContext,
+ throw_on_resolution_failure: bool,
+ memo: Optional[Set[int]],
+ ) -> Optional["Node"]:
+ """
+ Resolve an interpolation.
+
+ This happens in two steps:
+ 1. The parse tree is visited, which outputs either a `Node` (e.g.,
+ for node interpolations "${foo}"), a string (e.g., for string
+ interpolations "hello ${name}", or any other arbitrary value
+ (e.g., or custom interpolations "${foo:bar}").
+ 2. This output is potentially validated and converted when the node
+ being resolved (`value`) is typed.
+
+ If an error occurs in one of the above steps, an `InterpolationResolutionError`
+ (or a subclass of it) is raised, *unless* `throw_on_resolution_failure` is set
+ to `False` (in which case the return value is `None`).
+
+ :param parent: Parent of the node being resolved.
+ :param value: Node being resolved.
+ :param key: The associated key in the parent.
+ :param parse_tree: The parse tree as obtained from `grammar_parser.parse()`.
+ :param throw_on_resolution_failure: If `False`, then exceptions raised during
+ the resolution of the interpolation are silenced, and instead `None` is
+ returned.
+
+ :return: A `Node` that contains the interpolation result. This may be an existing
+ node in the config (in the case of a node interpolation "${foo}"), or a new
+ node that is created to wrap the interpolated value. It is `None` if and only if
+ `throw_on_resolution_failure` is `False` and an error occurs during resolution.
+ """
+
+ try:
+ resolved = self.resolve_parse_tree(
+ parse_tree=parse_tree, node=value, key=key, memo=memo
+ )
+ except InterpolationResolutionError:
+ if throw_on_resolution_failure:
+ raise
+ return None
+
+ return self._validate_and_convert_interpolation_result(
+ parent=parent,
+ value=value,
+ key=key,
+ resolved=resolved,
+ throw_on_resolution_failure=throw_on_resolution_failure,
+ )
+
+ def _validate_and_convert_interpolation_result(
+ self,
+ parent: Optional["Container"],
+ value: "Node",
+ key: Any,
+ resolved: Any,
+ throw_on_resolution_failure: bool,
+ ) -> Optional["Node"]:
+ from .nodes import AnyNode, InterpolationResultNode, ValueNode
+
+ # If the output is not a Node already (e.g., because it is the output of a
+ # custom resolver), then we will need to wrap it within a Node.
+ must_wrap = not isinstance(resolved, Node)
+
+ # If the node is typed, validate (and possibly convert) the result.
+ if isinstance(value, ValueNode) and not isinstance(value, AnyNode):
+ res_value = _get_value(resolved)
+ try:
+ conv_value = value.validate_and_convert(res_value)
+ except ValidationError as e:
+ if throw_on_resolution_failure:
+ self._format_and_raise(
+ key=key,
+ value=res_value,
+ cause=e,
+ msg=f"While dereferencing interpolation '{value}': {e}",
+ type_override=InterpolationValidationError,
+ )
+ return None
+
+ # If the converted value is of the same type, it means that no conversion
+ # was actually needed. As a result, we can keep the original `resolved`
+ # (and otherwise, the converted value must be wrapped into a new node).
+ if type(conv_value) != type(res_value):
+ must_wrap = True
+ resolved = conv_value
+
+ if must_wrap:
+ return InterpolationResultNode(value=resolved, key=key, parent=parent)
+ else:
+ assert isinstance(resolved, Node)
+ return resolved
+
+ def _validate_not_dereferencing_to_parent(self, node: Node, target: Node) -> None:
+ parent: Optional[Node] = node
+ while parent is not None:
+ if parent is target:
+ raise InterpolationResolutionError(
+ "Interpolation to parent node detected"
+ )
+ parent = parent._get_parent()
+
+ def _resolve_node_interpolation(
+ self, inter_key: str, memo: Optional[Set[int]]
+ ) -> "Node":
+ """A node interpolation is of the form `${foo.bar}`"""
+ try:
+ root_node, inter_key = self._resolve_key_and_root(inter_key)
+ except ConfigKeyError as exc:
+ raise InterpolationKeyError(
+ f"ConfigKeyError while resolving interpolation: {exc}"
+ ).with_traceback(sys.exc_info()[2])
+
+ try:
+ parent, last_key, value = root_node._select_impl(
+ inter_key,
+ throw_on_missing=True,
+ throw_on_resolution_failure=True,
+ memo=memo,
+ )
+ except MissingMandatoryValue as exc:
+ raise InterpolationToMissingValueError(
+ f"MissingMandatoryValue while resolving interpolation: {exc}"
+ ).with_traceback(sys.exc_info()[2])
+
+ if parent is None or value is None:
+ raise InterpolationKeyError(f"Interpolation key '{inter_key}' not found")
+ else:
+ self._validate_not_dereferencing_to_parent(node=self, target=value)
+ return value
+
+ def _evaluate_custom_resolver(
+ self,
+ key: Any,
+ node: Node,
+ inter_type: str,
+ inter_args: Tuple[Any, ...],
+ inter_args_str: Tuple[str, ...],
+ ) -> Any:
+ from omegaconf import OmegaConf
+
+ resolver = OmegaConf._get_resolver(inter_type)
+ if resolver is not None:
+ root_node = self._get_root()
+ return resolver(
+ root_node,
+ self,
+ node,
+ inter_args,
+ inter_args_str,
+ )
+ else:
+ raise UnsupportedInterpolationType(
+ f"Unsupported interpolation type {inter_type}"
+ )
+
+ def _maybe_resolve_interpolation(
+ self,
+ parent: Optional["Container"],
+ key: Any,
+ value: Node,
+ throw_on_resolution_failure: bool,
+ memo: Optional[Set[int]] = None,
+ ) -> Optional[Node]:
+ value_kind = get_value_kind(value)
+ if value_kind != ValueKind.INTERPOLATION:
+ return value
+
+ parse_tree = parse(_get_value(value))
+ return self._resolve_interpolation_from_parse_tree(
+ parent=parent,
+ value=value,
+ key=key,
+ parse_tree=parse_tree,
+ throw_on_resolution_failure=throw_on_resolution_failure,
+ memo=memo if memo is not None else set(),
+ )
+
+ def resolve_parse_tree(
+ self,
+ parse_tree: ParserRuleContext,
+ node: Node,
+ memo: Optional[Set[int]] = None,
+ key: Optional[Any] = None,
+ ) -> Any:
+ """
+ Resolve a given parse tree into its value.
+
+ We make no assumption here on the type of the tree's root, so that the
+ return value may be of any type.
+ """
+
+ def node_interpolation_callback(
+ inter_key: str, memo: Optional[Set[int]]
+ ) -> Optional["Node"]:
+ return self._resolve_node_interpolation(inter_key=inter_key, memo=memo)
+
+ def resolver_interpolation_callback(
+ name: str, args: Tuple[Any, ...], args_str: Tuple[str, ...]
+ ) -> Any:
+ return self._evaluate_custom_resolver(
+ key=key,
+ node=node,
+ inter_type=name,
+ inter_args=args,
+ inter_args_str=args_str,
+ )
+
+ visitor = GrammarVisitor(
+ node_interpolation_callback=node_interpolation_callback,
+ resolver_interpolation_callback=resolver_interpolation_callback,
+ memo=memo,
+ )
+ try:
+ return visitor.visit(parse_tree)
+ except InterpolationResolutionError:
+ raise
+ except Exception as exc:
+ # Other kinds of exceptions are wrapped in an `InterpolationResolutionError`.
+ raise InterpolationResolutionError(
+ f"{type(exc).__name__} raised while resolving interpolation: {exc}"
+ ).with_traceback(sys.exc_info()[2])
+
+ def _invalidate_flags_cache(self) -> None:
+ from .dictconfig import DictConfig
+ from .listconfig import ListConfig
+
+ # invalidate subtree cache only if the cache is initialized in this node.
+
+ if self.__dict__["_flags_cache"] is not None:
+ self.__dict__["_flags_cache"] = None
+ if isinstance(self, DictConfig):
+ content = self.__dict__["_content"]
+ if isinstance(content, dict):
+ for value in self.__dict__["_content"].values():
+ value._invalidate_flags_cache()
+ elif isinstance(self, ListConfig):
+ content = self.__dict__["_content"]
+ if isinstance(content, list):
+ for item in self.__dict__["_content"]:
+ item._invalidate_flags_cache()
+
+
+class SCMode(Enum):
+ DICT = 1 # Convert to plain dict
+ DICT_CONFIG = 2 # Keep as OmegaConf DictConfig
+ INSTANTIATE = 3 # Create a dataclass or attrs class instance
+
+
+class UnionNode(Box):
+ """
+ This class handles Union type hints. The `_content` attribute is either a
+ child node that is compatible with the given Union ref_type, or it is a
+ special value (None or MISSING or interpolation).
+
+ Much of the logic for e.g. value assignment and type validation is
+ delegated to the child node. As such, UnionNode functions as a
+ "pass-through" node. User apps and downstream libraries should not need to
+ know about UnionNode (assuming they only use OmegaConf's public API).
+ """
+
+ _parent: Optional[Container]
+ _content: Union[Node, None, str]
+
+ def __init__(
+ self,
+ content: Any,
+ ref_type: Any,
+ is_optional: bool = True,
+ key: Any = None,
+ parent: Optional[Box] = None,
+ ) -> None:
+ try:
+ if not is_union_annotation(ref_type): # pragma: no cover
+ msg = (
+ f"UnionNode got unexpected ref_type {ref_type}. Please file a bug"
+ + " report at https://github.com/omry/omegaconf/issues"
+ )
+ raise AssertionError(msg)
+ if not isinstance(parent, (Container, NoneType)):
+ raise ConfigTypeError("Parent type is not omegaconf.Container")
+ super().__init__(
+ parent=parent,
+ metadata=Metadata(
+ ref_type=ref_type,
+ object_type=None,
+ optional=is_optional,
+ key=key,
+ flags={"convert": False},
+ ),
+ )
+ self._set_value(content)
+ except Exception as ex:
+ format_and_raise(node=None, key=key, value=content, msg=str(ex), cause=ex)
+
+ def _get_full_key(self, key: Optional[Union[DictKeyType, int]]) -> str:
+ parent = self._get_parent()
+ if parent is None:
+ if self._metadata.key is None:
+ return ""
+ else:
+ return str(self._metadata.key)
+ else:
+ return parent._get_full_key(self._metadata.key)
+
+ def __eq__(self, other: Any) -> bool:
+ content = self.__dict__["_content"]
+ if isinstance(content, Node):
+ ret = content.__eq__(other)
+ elif isinstance(other, Node):
+ ret = other.__eq__(content)
+ else:
+ ret = content.__eq__(other)
+ assert isinstance(ret, (bool, type(NotImplemented)))
+ return ret
+
+ def __ne__(self, other: Any) -> bool:
+ x = self.__eq__(other)
+ if x is NotImplemented:
+ return NotImplemented
+ return not x
+
+ def __hash__(self) -> int:
+ return hash(self.__dict__["_content"])
+
+ def _value(self) -> Union[Node, None, str]:
+ content = self.__dict__["_content"]
+ assert isinstance(content, (Node, NoneType, str))
+ return content
+
+ def _set_value(self, value: Any, flags: Optional[Dict[str, bool]] = None) -> None:
+ previous_content = self.__dict__["_content"]
+ previous_metadata = self.__dict__["_metadata"]
+ try:
+ self._set_value_impl(value, flags)
+ except Exception as e:
+ self.__dict__["_content"] = previous_content
+ self.__dict__["_metadata"] = previous_metadata
+ raise e
+
+ def _set_value_impl(
+ self, value: Any, flags: Optional[Dict[str, bool]] = None
+ ) -> None:
+ from omegaconf.omegaconf import _node_wrap
+
+ ref_type = self._metadata.ref_type
+ type_hint = self._metadata.type_hint
+
+ value = _get_value(value)
+ if _is_special(value):
+ assert isinstance(value, (str, NoneType))
+ if value is None:
+ if not self._is_optional():
+ raise ValidationError(
+ f"Value '$VALUE' is incompatible with type hint '{type_str(type_hint)}'"
+ )
+ self.__dict__["_content"] = value
+ elif isinstance(value, Container):
+ raise ValidationError(
+ f"Cannot assign container '$VALUE' of type '$VALUE_TYPE' to {type_str(type_hint)}"
+ )
+ else:
+ for candidate_ref_type in ref_type.__args__:
+ try:
+ self.__dict__["_content"] = _node_wrap(
+ value=value,
+ ref_type=candidate_ref_type,
+ is_optional=False,
+ key=None,
+ parent=self,
+ )
+ break
+ except ValidationError:
+ continue
+ else:
+ raise ValidationError(
+ f"Value '$VALUE' of type '$VALUE_TYPE' is incompatible with type hint '{type_str(type_hint)}'"
+ )
+
+ def _is_optional(self) -> bool:
+ return self.__dict__["_metadata"].optional is True
+
+ def _is_interpolation(self) -> bool:
+ return _is_interpolation(self.__dict__["_content"])
+
+ def __str__(self) -> str:
+ return str(self.__dict__["_content"])
+
+ def __repr__(self) -> str:
+ return repr(self.__dict__["_content"])
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "UnionNode":
+ res = object.__new__(type(self))
+ for key, value in self.__dict__.items():
+ if key not in ("_content", "_parent"):
+ res.__dict__[key] = copy.deepcopy(value, memo=memo)
+
+ src_content = self.__dict__["_content"]
+ if isinstance(src_content, Node):
+ old_parent = src_content.__dict__["_parent"]
+ try:
+ src_content.__dict__["_parent"] = None
+ content_copy = copy.deepcopy(src_content, memo=memo)
+ content_copy.__dict__["_parent"] = res
+ finally:
+ src_content.__dict__["_parent"] = old_parent
+ else:
+ # None and strings can be assigned as is
+ content_copy = src_content
+
+ res.__dict__["_content"] = content_copy
+ res.__dict__["_parent"] = self.__dict__["_parent"]
+ return res
diff --git a/cameras/deps/omegaconf/basecontainer.py b/cameras/deps/omegaconf/basecontainer.py
new file mode 100644
index 0000000000000000000000000000000000000000..575df3c72105f9563d29bca8077d7e3fb79742b2
--- /dev/null
+++ b/cameras/deps/omegaconf/basecontainer.py
@@ -0,0 +1,916 @@
+import copy
+import sys
+from abc import ABC, abstractmethod
+from enum import Enum
+from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Tuple, Union
+
+import yaml
+
+from ._utils import (
+ _DEFAULT_MARKER_,
+ ValueKind,
+ _ensure_container,
+ _get_value,
+ _is_interpolation,
+ _is_missing_value,
+ _is_none,
+ _is_special,
+ _resolve_optional,
+ get_structured_config_data,
+ get_type_hint,
+ get_value_kind,
+ get_yaml_loader,
+ is_container_annotation,
+ is_dict_annotation,
+ is_list_annotation,
+ is_primitive_dict,
+ is_primitive_type_annotation,
+ is_structured_config,
+ is_tuple_annotation,
+ is_union_annotation,
+)
+from .base import (
+ Box,
+ Container,
+ ContainerMetadata,
+ DictKeyType,
+ Node,
+ SCMode,
+ UnionNode,
+)
+from .errors import (
+ ConfigCycleDetectedException,
+ ConfigTypeError,
+ InterpolationResolutionError,
+ KeyValidationError,
+ MissingMandatoryValue,
+ OmegaConfBaseException,
+ ReadonlyConfigError,
+ ValidationError,
+)
+
+if TYPE_CHECKING:
+ from .dictconfig import DictConfig # pragma: no cover
+
+
+class BaseContainer(Container, ABC):
+ _resolvers: ClassVar[Dict[str, Any]] = {}
+
+ def __init__(self, parent: Optional[Box], metadata: ContainerMetadata):
+ if not (parent is None or isinstance(parent, Box)):
+ raise ConfigTypeError("Parent type is not omegaconf.Box")
+ super().__init__(parent=parent, metadata=metadata)
+
+ def _get_child(
+ self,
+ key: Any,
+ validate_access: bool = True,
+ validate_key: bool = True,
+ throw_on_missing_value: bool = False,
+ throw_on_missing_key: bool = False,
+ ) -> Union[Optional[Node], List[Optional[Node]]]:
+ """Like _get_node, passing through to the nearest concrete Node."""
+ child = self._get_node(
+ key=key,
+ validate_access=validate_access,
+ validate_key=validate_key,
+ throw_on_missing_value=throw_on_missing_value,
+ throw_on_missing_key=throw_on_missing_key,
+ )
+ if isinstance(child, UnionNode) and not _is_special(child):
+ value = child._value()
+ assert isinstance(value, Node) and not isinstance(value, UnionNode)
+ child = value
+ return child
+
+ def _resolve_with_default(
+ self,
+ key: Union[DictKeyType, int],
+ value: Node,
+ default_value: Any = _DEFAULT_MARKER_,
+ ) -> Any:
+ """returns the value with the specified key, like obj.key and obj['key']"""
+ if _is_missing_value(value):
+ if default_value is not _DEFAULT_MARKER_:
+ return default_value
+ raise MissingMandatoryValue("Missing mandatory value: $FULL_KEY")
+
+ resolved_node = self._maybe_resolve_interpolation(
+ parent=self,
+ key=key,
+ value=value,
+ throw_on_resolution_failure=True,
+ )
+
+ return _get_value(resolved_node)
+
+ def __str__(self) -> str:
+ return self.__repr__()
+
+ def __repr__(self) -> str:
+ if self.__dict__["_content"] is None:
+ return "None"
+ elif self._is_interpolation() or self._is_missing():
+ v = self.__dict__["_content"]
+ return f"'{v}'"
+ else:
+ return self.__dict__["_content"].__repr__() # type: ignore
+
+ # Support pickle
+ def __getstate__(self) -> Dict[str, Any]:
+ dict_copy = copy.copy(self.__dict__)
+
+ # no need to serialize the flags cache, it can be re-constructed later
+ dict_copy.pop("_flags_cache", None)
+
+ dict_copy["_metadata"] = copy.copy(dict_copy["_metadata"])
+ ref_type = self._metadata.ref_type
+ if is_container_annotation(ref_type):
+ if is_dict_annotation(ref_type):
+ dict_copy["_metadata"].ref_type = Dict
+ elif is_list_annotation(ref_type):
+ dict_copy["_metadata"].ref_type = List
+ else:
+ assert False
+ if sys.version_info < (3, 7): # pragma: no cover
+ element_type = self._metadata.element_type
+ if is_union_annotation(element_type):
+ raise OmegaConfBaseException(
+ "Serializing structured configs with `Union` element type requires python >= 3.7"
+ )
+ return dict_copy
+
+ # Support pickle
+ def __setstate__(self, d: Dict[str, Any]) -> None:
+ from omegaconf import DictConfig
+ from omegaconf._utils import is_generic_dict, is_generic_list
+
+ if isinstance(self, DictConfig):
+ key_type = d["_metadata"].key_type
+
+ # backward compatibility to load OmegaConf 2.0 configs
+ if key_type is None:
+ key_type = Any
+ d["_metadata"].key_type = key_type
+
+ element_type = d["_metadata"].element_type
+
+ # backward compatibility to load OmegaConf 2.0 configs
+ if element_type is None:
+ element_type = Any
+ d["_metadata"].element_type = element_type
+
+ ref_type = d["_metadata"].ref_type
+ if is_container_annotation(ref_type):
+ if is_generic_dict(ref_type):
+ d["_metadata"].ref_type = Dict[key_type, element_type] # type: ignore
+ elif is_generic_list(ref_type):
+ d["_metadata"].ref_type = List[element_type] # type: ignore
+ else:
+ assert False
+
+ d["_flags_cache"] = None
+ self.__dict__.update(d)
+
+ @abstractmethod
+ def __delitem__(self, key: Any) -> None:
+ ...
+
+ def __len__(self) -> int:
+ if self._is_none() or self._is_missing() or self._is_interpolation():
+ return 0
+ content = self.__dict__["_content"]
+ return len(content)
+
+ def merge_with_cli(self) -> None:
+ args_list = sys.argv[1:]
+ self.merge_with_dotlist(args_list)
+
+ def merge_with_dotlist(self, dotlist: List[str]) -> None:
+ from omegaconf import OmegaConf
+
+ def fail() -> None:
+ raise ValueError("Input list must be a list or a tuple of strings")
+
+ if not isinstance(dotlist, (list, tuple)):
+ fail()
+
+ for arg in dotlist:
+ if not isinstance(arg, str):
+ fail()
+
+ idx = arg.find("=")
+ if idx == -1:
+ key = arg
+ value = None
+ else:
+ key = arg[0:idx]
+ value = arg[idx + 1 :]
+ value = yaml.load(value, Loader=get_yaml_loader())
+
+ OmegaConf.update(self, key, value)
+
+ def is_empty(self) -> bool:
+ """return true if config is empty"""
+ return len(self.__dict__["_content"]) == 0
+
+ @staticmethod
+ def _to_content(
+ conf: Container,
+ resolve: bool,
+ throw_on_missing: bool,
+ enum_to_str: bool = False,
+ structured_config_mode: SCMode = SCMode.DICT,
+ ) -> Union[None, Any, str, Dict[DictKeyType, Any], List[Any]]:
+ from omegaconf import MISSING, DictConfig, ListConfig
+
+ def convert(val: Node) -> Any:
+ value = val._value()
+ if enum_to_str and isinstance(value, Enum):
+ value = f"{value.name}"
+
+ return value
+
+ def get_node_value(key: Union[DictKeyType, int]) -> Any:
+ try:
+ node = conf._get_child(key, throw_on_missing_value=throw_on_missing)
+ except MissingMandatoryValue as e:
+ conf._format_and_raise(key=key, value=None, cause=e)
+ assert isinstance(node, Node)
+ if resolve:
+ try:
+ node = node._dereference_node()
+ except InterpolationResolutionError as e:
+ conf._format_and_raise(key=key, value=None, cause=e)
+
+ if isinstance(node, Container):
+ value = BaseContainer._to_content(
+ node,
+ resolve=resolve,
+ throw_on_missing=throw_on_missing,
+ enum_to_str=enum_to_str,
+ structured_config_mode=structured_config_mode,
+ )
+ else:
+ value = convert(node)
+ return value
+
+ if conf._is_none():
+ return None
+ elif conf._is_missing():
+ if throw_on_missing:
+ conf._format_and_raise(
+ key=None,
+ value=None,
+ cause=MissingMandatoryValue("Missing mandatory value"),
+ )
+ else:
+ return MISSING
+ elif not resolve and conf._is_interpolation():
+ inter = conf._value()
+ assert isinstance(inter, str)
+ return inter
+
+ if resolve:
+ _conf = conf._dereference_node()
+ assert isinstance(_conf, Container)
+ conf = _conf
+
+ if isinstance(conf, DictConfig):
+ if (
+ conf._metadata.object_type not in (dict, None)
+ and structured_config_mode == SCMode.DICT_CONFIG
+ ):
+ return conf
+ if structured_config_mode == SCMode.INSTANTIATE and is_structured_config(
+ conf._metadata.object_type
+ ):
+ return conf._to_object()
+
+ retdict: Dict[DictKeyType, Any] = {}
+ for key in conf.keys():
+ value = get_node_value(key)
+ if enum_to_str and isinstance(key, Enum):
+ key = f"{key.name}"
+ retdict[key] = value
+ return retdict
+ elif isinstance(conf, ListConfig):
+ retlist: List[Any] = []
+ for index in range(len(conf)):
+ item = get_node_value(index)
+ retlist.append(item)
+
+ return retlist
+ assert False
+
+ @staticmethod
+ def _map_merge(dest: "BaseContainer", src: "BaseContainer") -> None:
+ """merge src into dest and return a new copy, does not modified input"""
+ from omegaconf import AnyNode, DictConfig, ValueNode
+
+ assert isinstance(dest, DictConfig)
+ assert isinstance(src, DictConfig)
+ src_type = src._metadata.object_type
+ src_ref_type = get_type_hint(src)
+ assert src_ref_type is not None
+
+ # If source DictConfig is:
+ # - None => set the destination DictConfig to None
+ # - an interpolation => set the destination DictConfig to be the same interpolation
+ if src._is_none() or src._is_interpolation():
+ dest._set_value(src._value())
+ _update_types(node=dest, ref_type=src_ref_type, object_type=src_type)
+ return
+
+ dest._validate_merge(value=src)
+
+ def expand(node: Container) -> None:
+ rt = node._metadata.ref_type
+ val: Any
+ if rt is not Any:
+ if is_dict_annotation(rt):
+ val = {}
+ elif is_list_annotation(rt) or is_tuple_annotation(rt):
+ val = []
+ else:
+ val = rt
+ elif isinstance(node, DictConfig):
+ val = {}
+ else:
+ assert False
+
+ node._set_value(val)
+
+ if (
+ src._is_missing()
+ and not dest._is_missing()
+ and is_structured_config(src_ref_type)
+ ):
+ # Replace `src` with a prototype of its corresponding structured config
+ # whose fields are all missing (to avoid overwriting fields in `dest`).
+ assert src_type is None # src missing, so src's object_type should be None
+ src_type = src_ref_type
+ src = _create_structured_with_missing_fields(
+ ref_type=src_ref_type, object_type=src_type
+ )
+
+ if (dest._is_interpolation() or dest._is_missing()) and not src._is_missing():
+ expand(dest)
+
+ src_items = list(src) if not src._is_missing() else []
+ for key in src_items:
+ src_node = src._get_node(key, validate_access=False)
+ dest_node = dest._get_node(key, validate_access=False)
+ assert isinstance(src_node, Node)
+ assert dest_node is None or isinstance(dest_node, Node)
+ src_value = _get_value(src_node)
+
+ src_vk = get_value_kind(src_node)
+ src_node_missing = src_vk is ValueKind.MANDATORY_MISSING
+
+ if isinstance(dest_node, DictConfig):
+ dest_node._validate_merge(value=src_node)
+
+ if (
+ isinstance(dest_node, Container)
+ and dest_node._is_none()
+ and not src_node_missing
+ and not _is_none(src_node, resolve=True)
+ ):
+ expand(dest_node)
+
+ if dest_node is not None and dest_node._is_interpolation():
+ target_node = dest_node._maybe_dereference_node()
+ if isinstance(target_node, Container):
+ dest[key] = target_node
+ dest_node = dest._get_node(key)
+
+ is_optional, et = _resolve_optional(dest._metadata.element_type)
+ if dest_node is None and is_structured_config(et) and not src_node_missing:
+ # merging into a new node. Use element_type as a base
+ dest[key] = DictConfig(
+ et, parent=dest, ref_type=et, is_optional=is_optional
+ )
+ dest_node = dest._get_node(key)
+
+ if dest_node is not None:
+ if isinstance(dest_node, BaseContainer):
+ if isinstance(src_node, BaseContainer):
+ dest_node._merge_with(src_node)
+ elif not src_node_missing:
+ dest.__setitem__(key, src_node)
+ else:
+ if isinstance(src_node, BaseContainer):
+ dest.__setitem__(key, src_node)
+ else:
+ assert isinstance(dest_node, (ValueNode, UnionNode))
+ assert isinstance(src_node, (ValueNode, UnionNode))
+ try:
+ if isinstance(dest_node, AnyNode):
+ if src_node_missing:
+ node = copy.copy(src_node)
+ # if src node is missing, use the value from the dest_node,
+ # but validate it against the type of the src node before assigment
+ node._set_value(dest_node._value())
+ else:
+ node = src_node
+ dest.__setitem__(key, node)
+ else:
+ if not src_node_missing:
+ dest_node._set_value(src_value)
+
+ except (ValidationError, ReadonlyConfigError) as e:
+ dest._format_and_raise(key=key, value=src_value, cause=e)
+ else:
+ from omegaconf import open_dict
+
+ if is_structured_config(src_type):
+ # verified to be compatible above in _validate_merge
+ with open_dict(dest):
+ dest[key] = src._get_node(key)
+ else:
+ dest[key] = src._get_node(key)
+
+ _update_types(node=dest, ref_type=src_ref_type, object_type=src_type)
+
+ # explicit flags on the source config are replacing the flag values in the destination
+ flags = src._metadata.flags
+ assert flags is not None
+ for flag, value in flags.items():
+ if value is not None:
+ dest._set_flag(flag, value)
+
+ @staticmethod
+ def _list_merge(dest: Any, src: Any) -> None:
+ from omegaconf import DictConfig, ListConfig, OmegaConf
+
+ assert isinstance(dest, ListConfig)
+ assert isinstance(src, ListConfig)
+
+ if src._is_none():
+ dest._set_value(None)
+ elif src._is_missing():
+ # do not change dest if src is MISSING.
+ if dest._metadata.element_type is Any:
+ dest._metadata.element_type = src._metadata.element_type
+ elif src._is_interpolation():
+ dest._set_value(src._value())
+ else:
+ temp_target = ListConfig(content=[], parent=dest._get_parent())
+ temp_target.__dict__["_metadata"] = copy.deepcopy(
+ dest.__dict__["_metadata"]
+ )
+ is_optional, et = _resolve_optional(dest._metadata.element_type)
+ if is_structured_config(et):
+ prototype = DictConfig(et, ref_type=et, is_optional=is_optional)
+ for item in src._iter_ex(resolve=False):
+ if isinstance(item, DictConfig):
+ item = OmegaConf.merge(prototype, item)
+ temp_target.append(item)
+ else:
+ for item in src._iter_ex(resolve=False):
+ temp_target.append(item)
+
+ dest.__dict__["_content"] = temp_target.__dict__["_content"]
+
+ # explicit flags on the source config are replacing the flag values in the destination
+ flags = src._metadata.flags
+ assert flags is not None
+ for flag, value in flags.items():
+ if value is not None:
+ dest._set_flag(flag, value)
+
+ def merge_with(
+ self,
+ *others: Union[
+ "BaseContainer", Dict[str, Any], List[Any], Tuple[Any, ...], Any
+ ],
+ ) -> None:
+ try:
+ self._merge_with(*others)
+ except Exception as e:
+ self._format_and_raise(key=None, value=None, cause=e)
+
+ def _merge_with(
+ self,
+ *others: Union[
+ "BaseContainer", Dict[str, Any], List[Any], Tuple[Any, ...], Any
+ ],
+ ) -> None:
+ from .dictconfig import DictConfig
+ from .listconfig import ListConfig
+
+ """merge a list of other Config objects into this one, overriding as needed"""
+ for other in others:
+ if other is None:
+ raise ValueError("Cannot merge with a None config")
+
+ my_flags = {}
+ if self._get_flag("allow_objects") is True:
+ my_flags = {"allow_objects": True}
+ other = _ensure_container(other, flags=my_flags)
+
+ if isinstance(self, DictConfig) and isinstance(other, DictConfig):
+ BaseContainer._map_merge(self, other)
+ elif isinstance(self, ListConfig) and isinstance(other, ListConfig):
+ BaseContainer._list_merge(self, other)
+ else:
+ raise TypeError("Cannot merge DictConfig with ListConfig")
+
+ # recursively correct the parent hierarchy after the merge
+ self._re_parent()
+
+ # noinspection PyProtectedMember
+ def _set_item_impl(self, key: Any, value: Any) -> None:
+ """
+ Changes the value of the node key with the desired value. If the node key doesn't
+ exist it creates a new one.
+ """
+ from .nodes import AnyNode, ValueNode
+
+ if isinstance(value, Node):
+ do_deepcopy = not self._get_flag("no_deepcopy_set_nodes")
+ if not do_deepcopy and isinstance(value, Box):
+ # if value is from the same config, perform a deepcopy no matter what.
+ if self._get_root() is value._get_root():
+ do_deepcopy = True
+
+ if do_deepcopy:
+ value = copy.deepcopy(value)
+ value._set_parent(None)
+
+ try:
+ old = value._key()
+ value._set_key(key)
+ self._validate_set(key, value)
+ finally:
+ value._set_key(old)
+ else:
+ self._validate_set(key, value)
+
+ if self._get_flag("readonly"):
+ raise ReadonlyConfigError("Cannot change read-only config container")
+
+ input_is_node = isinstance(value, Node)
+ target_node_ref = self._get_node(key)
+ assert target_node_ref is None or isinstance(target_node_ref, Node)
+
+ input_is_typed_vnode = isinstance(value, ValueNode) and not isinstance(
+ value, AnyNode
+ )
+
+ def get_target_type_hint(val: Any) -> Any:
+ if not is_structured_config(val):
+ type_hint = self._metadata.element_type
+ else:
+ target = self._get_node(key)
+ if target is None:
+ type_hint = self._metadata.element_type
+ else:
+ assert isinstance(target, Node)
+ type_hint = target._metadata.type_hint
+ return type_hint
+
+ target_type_hint = get_target_type_hint(value)
+ _, target_ref_type = _resolve_optional(target_type_hint)
+
+ def assign(value_key: Any, val: Node) -> None:
+ assert val._get_parent() is None
+ v = val
+ v._set_parent(self)
+ v._set_key(value_key)
+ _deep_update_type_hint(node=v, type_hint=self._metadata.element_type)
+ self.__dict__["_content"][value_key] = v
+
+ if input_is_typed_vnode and not is_union_annotation(target_ref_type):
+ assign(key, value)
+ else:
+ # input is not a ValueNode, can be primitive or box
+
+ special_value = _is_special(value)
+ # We use the `Node._set_value` method if the target node exists and:
+ # 1. the target has an explicit ref_type, or
+ # 2. the target is an AnyNode and the input is a primitive type.
+ should_set_value = target_node_ref is not None and (
+ target_node_ref._has_ref_type()
+ or (
+ isinstance(target_node_ref, AnyNode)
+ and is_primitive_type_annotation(value)
+ )
+ )
+ if should_set_value:
+ if special_value and isinstance(value, Node):
+ value = value._value()
+ self.__dict__["_content"][key]._set_value(value)
+ elif input_is_node:
+ if (
+ special_value
+ and (
+ is_container_annotation(target_ref_type)
+ or is_structured_config(target_ref_type)
+ )
+ or is_primitive_type_annotation(target_ref_type)
+ or is_union_annotation(target_ref_type)
+ ):
+ value = _get_value(value)
+ self._wrap_value_and_set(key, value, target_type_hint)
+ else:
+ assign(key, value)
+ else:
+ self._wrap_value_and_set(key, value, target_type_hint)
+
+ def _wrap_value_and_set(self, key: Any, val: Any, type_hint: Any) -> None:
+ from omegaconf.omegaconf import _maybe_wrap
+
+ is_optional, ref_type = _resolve_optional(type_hint)
+
+ try:
+ wrapped = _maybe_wrap(
+ ref_type=ref_type,
+ key=key,
+ value=val,
+ is_optional=is_optional,
+ parent=self,
+ )
+ except ValidationError as e:
+ self._format_and_raise(key=key, value=val, cause=e)
+ self.__dict__["_content"][key] = wrapped
+
+ @staticmethod
+ def _item_eq(
+ c1: Container,
+ k1: Union[DictKeyType, int],
+ c2: Container,
+ k2: Union[DictKeyType, int],
+ ) -> bool:
+ v1 = c1._get_child(k1)
+ v2 = c2._get_child(k2)
+ assert v1 is not None and v2 is not None
+
+ assert isinstance(v1, Node)
+ assert isinstance(v2, Node)
+
+ if v1._is_none() and v2._is_none():
+ return True
+
+ if v1._is_missing() and v2._is_missing():
+ return True
+
+ v1_inter = v1._is_interpolation()
+ v2_inter = v2._is_interpolation()
+ dv1: Optional[Node] = v1
+ dv2: Optional[Node] = v2
+
+ if v1_inter:
+ dv1 = v1._maybe_dereference_node()
+ if v2_inter:
+ dv2 = v2._maybe_dereference_node()
+
+ if v1_inter and v2_inter:
+ if dv1 is None or dv2 is None:
+ return v1 == v2
+ else:
+ # both are not none, if both are containers compare as container
+ if isinstance(dv1, Container) and isinstance(dv2, Container):
+ if dv1 != dv2:
+ return False
+ dv1 = _get_value(dv1)
+ dv2 = _get_value(dv2)
+ return dv1 == dv2
+ elif not v1_inter and not v2_inter:
+ v1 = _get_value(v1)
+ v2 = _get_value(v2)
+ ret = v1 == v2
+ assert isinstance(ret, bool)
+ return ret
+ else:
+ dv1 = _get_value(dv1)
+ dv2 = _get_value(dv2)
+ ret = dv1 == dv2
+ assert isinstance(ret, bool)
+ return ret
+
+ def _is_optional(self) -> bool:
+ return self.__dict__["_metadata"].optional is True
+
+ def _is_interpolation(self) -> bool:
+ return _is_interpolation(self.__dict__["_content"])
+
+ @abstractmethod
+ def _validate_get(self, key: Any, value: Any = None) -> None:
+ ...
+
+ @abstractmethod
+ def _validate_set(self, key: Any, value: Any) -> None:
+ ...
+
+ def _value(self) -> Any:
+ return self.__dict__["_content"]
+
+ def _get_full_key(self, key: Union[DictKeyType, int, slice, None]) -> str:
+ from .listconfig import ListConfig
+ from .omegaconf import _select_one
+
+ if not isinstance(key, (int, str, Enum, float, bool, slice, bytes, type(None))):
+ return ""
+
+ def _slice_to_str(x: slice) -> str:
+ if x.step is not None:
+ return f"{x.start}:{x.stop}:{x.step}"
+ else:
+ return f"{x.start}:{x.stop}"
+
+ def prepand(
+ full_key: str,
+ parent_type: Any,
+ cur_type: Any,
+ key: Optional[Union[DictKeyType, int, slice]],
+ ) -> str:
+ if key is None:
+ return full_key
+
+ if isinstance(key, slice):
+ key = _slice_to_str(key)
+ elif isinstance(key, Enum):
+ key = key.name
+ else:
+ key = str(key)
+
+ assert isinstance(key, str)
+
+ if issubclass(parent_type, ListConfig):
+ if full_key != "":
+ if issubclass(cur_type, ListConfig):
+ full_key = f"[{key}]{full_key}"
+ else:
+ full_key = f"[{key}].{full_key}"
+ else:
+ full_key = f"[{key}]"
+ else:
+ if full_key == "":
+ full_key = key
+ else:
+ if issubclass(cur_type, ListConfig):
+ full_key = f"{key}{full_key}"
+ else:
+ full_key = f"{key}.{full_key}"
+ return full_key
+
+ if key is not None and key != "":
+ assert isinstance(self, Container)
+ cur, _ = _select_one(
+ c=self, key=str(key), throw_on_missing=False, throw_on_type_error=False
+ )
+ if cur is None:
+ cur = self
+ full_key = prepand("", type(cur), None, key)
+ if cur._key() is not None:
+ full_key = prepand(
+ full_key, type(cur._get_parent()), type(cur), cur._key()
+ )
+ else:
+ full_key = prepand("", type(cur._get_parent()), type(cur), cur._key())
+ else:
+ cur = self
+ if cur._key() is None:
+ return ""
+ full_key = self._key()
+
+ assert cur is not None
+ memo = {id(cur)} # remember already visited nodes so as to detect cycles
+ while cur._get_parent() is not None:
+ cur = cur._get_parent()
+ if id(cur) in memo:
+ raise ConfigCycleDetectedException(
+ f"Cycle when iterating over parents of key `{key!s}`"
+ )
+ memo.add(id(cur))
+ assert cur is not None
+ if cur._key() is not None:
+ full_key = prepand(
+ full_key, type(cur._get_parent()), type(cur), cur._key()
+ )
+
+ return full_key
+
+
+def _create_structured_with_missing_fields(
+ ref_type: type, object_type: Optional[type] = None
+) -> "DictConfig":
+ from . import MISSING, DictConfig
+
+ cfg_data = get_structured_config_data(ref_type)
+ for v in cfg_data.values():
+ v._set_value(MISSING)
+
+ cfg = DictConfig(cfg_data)
+ cfg._metadata.optional, cfg._metadata.ref_type = _resolve_optional(ref_type)
+ cfg._metadata.object_type = object_type
+
+ return cfg
+
+
+def _update_types(node: Node, ref_type: Any, object_type: Optional[type]) -> None:
+ if object_type is not None and not is_primitive_dict(object_type):
+ node._metadata.object_type = object_type
+
+ if node._metadata.ref_type is Any:
+ _deep_update_type_hint(node, ref_type)
+
+
+def _deep_update_type_hint(node: Node, type_hint: Any) -> None:
+ """Ensure node is compatible with type_hint, mutating if necessary."""
+ from omegaconf import DictConfig, ListConfig
+
+ from ._utils import get_dict_key_value_types, get_list_element_type
+
+ if type_hint is Any:
+ return
+
+ _shallow_validate_type_hint(node, type_hint)
+
+ new_is_optional, new_ref_type = _resolve_optional(type_hint)
+ node._metadata.ref_type = new_ref_type
+ node._metadata.optional = new_is_optional
+
+ if is_list_annotation(new_ref_type) and isinstance(node, ListConfig):
+ new_element_type = get_list_element_type(new_ref_type)
+ node._metadata.element_type = new_element_type
+ if not _is_special(node):
+ for i in range(len(node)):
+ _deep_update_subnode(node, i, new_element_type)
+
+ if is_dict_annotation(new_ref_type) and isinstance(node, DictConfig):
+ new_key_type, new_element_type = get_dict_key_value_types(new_ref_type)
+ node._metadata.key_type = new_key_type
+ node._metadata.element_type = new_element_type
+ if not _is_special(node):
+ for key in node:
+ if new_key_type is not Any and not isinstance(key, new_key_type):
+ raise KeyValidationError(
+ f"Key {key!r} ({type(key).__name__}) is incompatible"
+ + f" with key type hint '{new_key_type.__name__}'"
+ )
+ _deep_update_subnode(node, key, new_element_type)
+
+
+def _deep_update_subnode(node: BaseContainer, key: Any, value_type_hint: Any) -> None:
+ """Get node[key] and ensure it is compatible with value_type_hint, mutating if necessary."""
+ subnode = node._get_node(key)
+ assert isinstance(subnode, Node)
+ if _is_special(subnode):
+ # Ensure special values are wrapped in a Node subclass that
+ # is compatible with the type hint.
+ node._wrap_value_and_set(key, subnode._value(), value_type_hint)
+ subnode = node._get_node(key)
+ assert isinstance(subnode, Node)
+ _deep_update_type_hint(subnode, value_type_hint)
+
+
+def _shallow_validate_type_hint(node: Node, type_hint: Any) -> None:
+ """Error if node's type, content and metadata are not compatible with type_hint."""
+ from omegaconf import DictConfig, ListConfig, ValueNode
+
+ is_optional, ref_type = _resolve_optional(type_hint)
+
+ vk = get_value_kind(node)
+
+ if node._is_none():
+ if not is_optional:
+ value = _get_value(node)
+ raise ValidationError(
+ f"Value {value!r} ({type(value).__name__})"
+ + f" is incompatible with type hint '{ref_type.__name__}'"
+ )
+ return
+ elif vk in (ValueKind.MANDATORY_MISSING, ValueKind.INTERPOLATION):
+ return
+ elif vk == ValueKind.VALUE:
+ if is_primitive_type_annotation(ref_type) and isinstance(node, ValueNode):
+ value = node._value()
+ if not isinstance(value, ref_type):
+ raise ValidationError(
+ f"Value {value!r} ({type(value).__name__})"
+ + f" is incompatible with type hint '{ref_type.__name__}'"
+ )
+ elif is_structured_config(ref_type) and isinstance(node, DictConfig):
+ return
+ elif is_dict_annotation(ref_type) and isinstance(node, DictConfig):
+ return
+ elif is_list_annotation(ref_type) and isinstance(node, ListConfig):
+ return
+ else:
+ if isinstance(node, ValueNode):
+ value = node._value()
+ raise ValidationError(
+ f"Value {value!r} ({type(value).__name__})"
+ + f" is incompatible with type hint '{ref_type}'"
+ )
+ else:
+ raise ValidationError(
+ f"'{type(node).__name__}' is incompatible"
+ + f" with type hint '{ref_type}'"
+ )
+
+ else:
+ assert False
diff --git a/cameras/deps/omegaconf/dictconfig.py b/cameras/deps/omegaconf/dictconfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..da7c8957f1c2e30b4c1889997c85c2a33cf72f01
--- /dev/null
+++ b/cameras/deps/omegaconf/dictconfig.py
@@ -0,0 +1,776 @@
+import copy
+from enum import Enum
+from typing import (
+ Any,
+ Dict,
+ ItemsView,
+ Iterable,
+ Iterator,
+ KeysView,
+ List,
+ MutableMapping,
+ Optional,
+ Sequence,
+ Tuple,
+ Type,
+ Union,
+)
+
+from ._utils import (
+ _DEFAULT_MARKER_,
+ ValueKind,
+ _get_value,
+ _is_interpolation,
+ _is_missing_literal,
+ _is_missing_value,
+ _is_none,
+ _resolve_optional,
+ _valid_dict_key_annotation_type,
+ format_and_raise,
+ get_structured_config_data,
+ get_structured_config_init_field_names,
+ get_type_of,
+ get_value_kind,
+ is_container_annotation,
+ is_dict,
+ is_primitive_dict,
+ is_structured_config,
+ is_structured_config_frozen,
+ type_str,
+)
+from .base import Box, Container, ContainerMetadata, DictKeyType, Node
+from .basecontainer import BaseContainer
+from .errors import (
+ ConfigAttributeError,
+ ConfigKeyError,
+ ConfigTypeError,
+ InterpolationResolutionError,
+ KeyValidationError,
+ MissingMandatoryValue,
+ OmegaConfBaseException,
+ ReadonlyConfigError,
+ ValidationError,
+)
+from .nodes import EnumNode, ValueNode
+
+
+class DictConfig(BaseContainer, MutableMapping[Any, Any]):
+
+ _metadata: ContainerMetadata
+ _content: Union[Dict[DictKeyType, Node], None, str]
+
+ def __init__(
+ self,
+ content: Union[Dict[DictKeyType, Any], "DictConfig", Any],
+ key: Any = None,
+ parent: Optional[Box] = None,
+ ref_type: Union[Any, Type[Any]] = Any,
+ key_type: Union[Any, Type[Any]] = Any,
+ element_type: Union[Any, Type[Any]] = Any,
+ is_optional: bool = True,
+ flags: Optional[Dict[str, bool]] = None,
+ ) -> None:
+ try:
+ if isinstance(content, DictConfig):
+ if flags is None:
+ flags = content._metadata.flags
+ super().__init__(
+ parent=parent,
+ metadata=ContainerMetadata(
+ key=key,
+ optional=is_optional,
+ ref_type=ref_type,
+ object_type=dict,
+ key_type=key_type,
+ element_type=element_type,
+ flags=flags,
+ ),
+ )
+
+ if not _valid_dict_key_annotation_type(key_type):
+ raise KeyValidationError(f"Unsupported key type {key_type}")
+
+ if is_structured_config(content) or is_structured_config(ref_type):
+ self._set_value(content, flags=flags)
+ if is_structured_config_frozen(content) or is_structured_config_frozen(
+ ref_type
+ ):
+ self._set_flag("readonly", True)
+
+ else:
+ if isinstance(content, DictConfig):
+ metadata = copy.deepcopy(content._metadata)
+ metadata.key = key
+ metadata.ref_type = ref_type
+ metadata.optional = is_optional
+ metadata.element_type = element_type
+ metadata.key_type = key_type
+ self.__dict__["_metadata"] = metadata
+ self._set_value(content, flags=flags)
+ except Exception as ex:
+ format_and_raise(node=None, key=key, value=None, cause=ex, msg=str(ex))
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "DictConfig":
+ res = DictConfig(None)
+ res.__dict__["_metadata"] = copy.deepcopy(self.__dict__["_metadata"], memo=memo)
+ res.__dict__["_flags_cache"] = copy.deepcopy(
+ self.__dict__["_flags_cache"], memo=memo
+ )
+
+ src_content = self.__dict__["_content"]
+ if isinstance(src_content, dict):
+ content_copy = {}
+ for k, v in src_content.items():
+ old_parent = v.__dict__["_parent"]
+ try:
+ v.__dict__["_parent"] = None
+ vc = copy.deepcopy(v, memo=memo)
+ vc.__dict__["_parent"] = res
+ content_copy[k] = vc
+ finally:
+ v.__dict__["_parent"] = old_parent
+ else:
+ # None and strings can be assigned as is
+ content_copy = src_content
+
+ res.__dict__["_content"] = content_copy
+ # parent is retained, but not copied
+ res.__dict__["_parent"] = self.__dict__["_parent"]
+ return res
+
+ def copy(self) -> "DictConfig":
+ return copy.copy(self)
+
+ def _is_typed(self) -> bool:
+ return self._metadata.object_type not in (Any, None) and not is_dict(
+ self._metadata.object_type
+ )
+
+ def _validate_get(self, key: Any, value: Any = None) -> None:
+ is_typed = self._is_typed()
+
+ is_struct = self._get_flag("struct") is True
+ if key not in self.__dict__["_content"]:
+ if is_typed:
+ # do not raise an exception if struct is explicitly set to False
+ if self._get_node_flag("struct") is False:
+ return
+ if is_typed or is_struct:
+ if is_typed:
+ assert self._metadata.object_type not in (dict, None)
+ msg = f"Key '{key}' not in '{self._metadata.object_type.__name__}'"
+ else:
+ msg = f"Key '{key}' is not in struct"
+ self._format_and_raise(
+ key=key, value=value, cause=ConfigAttributeError(msg)
+ )
+
+ def _validate_set(self, key: Any, value: Any) -> None:
+ from omegaconf import OmegaConf
+
+ vk = get_value_kind(value)
+ if vk == ValueKind.INTERPOLATION:
+ return
+ if _is_none(value):
+ self._validate_non_optional(key, value)
+ return
+ if vk == ValueKind.MANDATORY_MISSING or value is None:
+ return
+
+ target = self._get_node(key) if key is not None else self
+
+ target_has_ref_type = isinstance(
+ target, DictConfig
+ ) and target._metadata.ref_type not in (Any, dict)
+ is_valid_target = target is None or not target_has_ref_type
+
+ if is_valid_target:
+ return
+
+ assert isinstance(target, Node)
+
+ target_type = target._metadata.ref_type
+ value_type = OmegaConf.get_type(value)
+
+ if is_dict(value_type) and is_dict(target_type):
+ return
+ if is_container_annotation(target_type) and not is_container_annotation(
+ value_type
+ ):
+ raise ValidationError(
+ f"Cannot assign {type_str(value_type)} to {type_str(target_type)}"
+ )
+
+ if target_type is not None and value_type is not None:
+ origin = getattr(target_type, "__origin__", target_type)
+ if not issubclass(value_type, origin):
+ self._raise_invalid_value(value, value_type, target_type)
+
+ def _validate_merge(self, value: Any) -> None:
+ from omegaconf import OmegaConf
+
+ dest = self
+ src = value
+
+ self._validate_non_optional(None, src)
+
+ dest_obj_type = OmegaConf.get_type(dest)
+ src_obj_type = OmegaConf.get_type(src)
+
+ if dest._is_missing() and src._metadata.object_type not in (dict, None):
+ self._validate_set(key=None, value=_get_value(src))
+
+ if src._is_missing():
+ return
+
+ validation_error = (
+ dest_obj_type is not None
+ and src_obj_type is not None
+ and is_structured_config(dest_obj_type)
+ and not src._is_none()
+ and not is_dict(src_obj_type)
+ and not issubclass(src_obj_type, dest_obj_type)
+ )
+ if validation_error:
+ msg = (
+ f"Merge error: {type_str(src_obj_type)} is not a "
+ f"subclass of {type_str(dest_obj_type)}. value: {src}"
+ )
+ raise ValidationError(msg)
+
+ def _validate_non_optional(self, key: Optional[DictKeyType], value: Any) -> None:
+ if _is_none(value, resolve=True, throw_on_resolution_failure=False):
+
+ if key is not None:
+ child = self._get_node(key)
+ if child is not None:
+ assert isinstance(child, Node)
+ field_is_optional = child._is_optional()
+ else:
+ field_is_optional, _ = _resolve_optional(
+ self._metadata.element_type
+ )
+ else:
+ field_is_optional = self._is_optional()
+
+ if not field_is_optional:
+ self._format_and_raise(
+ key=key,
+ value=value,
+ cause=ValidationError("field '$FULL_KEY' is not Optional"),
+ )
+
+ def _raise_invalid_value(
+ self, value: Any, value_type: Any, target_type: Any
+ ) -> None:
+ assert value_type is not None
+ assert target_type is not None
+ msg = (
+ f"Invalid type assigned: {type_str(value_type)} is not a "
+ f"subclass of {type_str(target_type)}. value: {value}"
+ )
+ raise ValidationError(msg)
+
+ def _validate_and_normalize_key(self, key: Any) -> DictKeyType:
+ return self._s_validate_and_normalize_key(self._metadata.key_type, key)
+
+ def _s_validate_and_normalize_key(self, key_type: Any, key: Any) -> DictKeyType:
+ if key_type is Any:
+ for t in DictKeyType.__args__: # type: ignore
+ if isinstance(key, t):
+ return key # type: ignore
+ raise KeyValidationError("Incompatible key type '$KEY_TYPE'")
+ elif key_type is bool and key in [0, 1]:
+ # Python treats True as 1 and False as 0 when used as dict keys
+ # assert hash(0) == hash(False)
+ # assert hash(1) == hash(True)
+ return bool(key)
+ elif key_type in (str, bytes, int, float, bool): # primitive type
+ if not isinstance(key, key_type):
+ raise KeyValidationError(
+ f"Key $KEY ($KEY_TYPE) is incompatible with ({key_type.__name__})"
+ )
+
+ return key # type: ignore
+ elif issubclass(key_type, Enum):
+ try:
+ return EnumNode.validate_and_convert_to_enum(key_type, key)
+ except ValidationError:
+ valid = ", ".join([x for x in key_type.__members__.keys()])
+ raise KeyValidationError(
+ f"Key '$KEY' is incompatible with the enum type '{key_type.__name__}', valid: [{valid}]"
+ )
+ else:
+ assert False, f"Unsupported key type {key_type}"
+
+ def __setitem__(self, key: DictKeyType, value: Any) -> None:
+ try:
+ self.__set_impl(key=key, value=value)
+ except AttributeError as e:
+ self._format_and_raise(
+ key=key, value=value, type_override=ConfigKeyError, cause=e
+ )
+ except Exception as e:
+ self._format_and_raise(key=key, value=value, cause=e)
+
+ def __set_impl(self, key: DictKeyType, value: Any) -> None:
+ key = self._validate_and_normalize_key(key)
+ self._set_item_impl(key, value)
+
+ # hide content while inspecting in debugger
+ def __dir__(self) -> Iterable[str]:
+ if self._is_missing() or self._is_none():
+ return []
+ return self.__dict__["_content"].keys() # type: ignore
+
+ def __setattr__(self, key: str, value: Any) -> None:
+ """
+ Allow assigning attributes to DictConfig
+ :param key:
+ :param value:
+ :return:
+ """
+ try:
+ self.__set_impl(key, value)
+ except Exception as e:
+ if isinstance(e, OmegaConfBaseException) and e._initialized:
+ raise e
+ self._format_and_raise(key=key, value=value, cause=e)
+ assert False
+
+ def __getattr__(self, key: str) -> Any:
+ """
+ Allow accessing dictionary values as attributes
+ :param key:
+ :return:
+ """
+ if key == "__name__":
+ raise AttributeError()
+
+ try:
+ return self._get_impl(
+ key=key, default_value=_DEFAULT_MARKER_, validate_key=False
+ )
+ except ConfigKeyError as e:
+ self._format_and_raise(
+ key=key, value=None, cause=e, type_override=ConfigAttributeError
+ )
+ except Exception as e:
+ self._format_and_raise(key=key, value=None, cause=e)
+
+ def __getitem__(self, key: DictKeyType) -> Any:
+ """
+ Allow map style access
+ :param key:
+ :return:
+ """
+
+ try:
+ return self._get_impl(key=key, default_value=_DEFAULT_MARKER_)
+ except AttributeError as e:
+ self._format_and_raise(
+ key=key, value=None, cause=e, type_override=ConfigKeyError
+ )
+ except Exception as e:
+ self._format_and_raise(key=key, value=None, cause=e)
+
+ def __delattr__(self, key: str) -> None:
+ """
+ Allow deleting dictionary values as attributes
+ :param key:
+ :return:
+ """
+ if self._get_flag("readonly"):
+ self._format_and_raise(
+ key=key,
+ value=None,
+ cause=ReadonlyConfigError(
+ "DictConfig in read-only mode does not support deletion"
+ ),
+ )
+ try:
+ del self.__dict__["_content"][key]
+ except KeyError:
+ msg = "Attribute not found: '$KEY'"
+ self._format_and_raise(key=key, value=None, cause=ConfigAttributeError(msg))
+
+ def __delitem__(self, key: DictKeyType) -> None:
+ key = self._validate_and_normalize_key(key)
+ if self._get_flag("readonly"):
+ self._format_and_raise(
+ key=key,
+ value=None,
+ cause=ReadonlyConfigError(
+ "DictConfig in read-only mode does not support deletion"
+ ),
+ )
+ if self._get_flag("struct"):
+ self._format_and_raise(
+ key=key,
+ value=None,
+ cause=ConfigTypeError(
+ "DictConfig in struct mode does not support deletion"
+ ),
+ )
+ if self._is_typed() and self._get_node_flag("struct") is not False:
+ self._format_and_raise(
+ key=key,
+ value=None,
+ cause=ConfigTypeError(
+ f"{type_str(self._metadata.object_type)} (DictConfig) does not support deletion"
+ ),
+ )
+
+ try:
+ del self.__dict__["_content"][key]
+ except KeyError:
+ msg = "Key not found: '$KEY'"
+ self._format_and_raise(key=key, value=None, cause=ConfigKeyError(msg))
+
+ def get(self, key: DictKeyType, default_value: Any = None) -> Any:
+ """Return the value for `key` if `key` is in the dictionary, else
+ `default_value` (defaulting to `None`)."""
+ try:
+ return self._get_impl(key=key, default_value=default_value)
+ except KeyValidationError as e:
+ self._format_and_raise(key=key, value=None, cause=e)
+
+ def _get_impl(
+ self, key: DictKeyType, default_value: Any, validate_key: bool = True
+ ) -> Any:
+ try:
+ node = self._get_child(
+ key=key, throw_on_missing_key=True, validate_key=validate_key
+ )
+ except (ConfigAttributeError, ConfigKeyError):
+ if default_value is not _DEFAULT_MARKER_:
+ return default_value
+ else:
+ raise
+ assert isinstance(node, Node)
+ return self._resolve_with_default(
+ key=key, value=node, default_value=default_value
+ )
+
+ def _get_node(
+ self,
+ key: DictKeyType,
+ validate_access: bool = True,
+ validate_key: bool = True,
+ throw_on_missing_value: bool = False,
+ throw_on_missing_key: bool = False,
+ ) -> Optional[Node]:
+ try:
+ key = self._validate_and_normalize_key(key)
+ except KeyValidationError:
+ if validate_access and validate_key:
+ raise
+ else:
+ if throw_on_missing_key:
+ raise ConfigAttributeError
+ else:
+ return None
+
+ if validate_access:
+ self._validate_get(key)
+
+ value: Optional[Node] = self.__dict__["_content"].get(key)
+ if value is None:
+ if throw_on_missing_key:
+ raise ConfigKeyError(f"Missing key {key!s}")
+ elif throw_on_missing_value and value._is_missing():
+ raise MissingMandatoryValue("Missing mandatory value: $KEY")
+ return value
+
+ def pop(self, key: DictKeyType, default: Any = _DEFAULT_MARKER_) -> Any:
+ try:
+ if self._get_flag("readonly"):
+ raise ReadonlyConfigError("Cannot pop from read-only node")
+ if self._get_flag("struct"):
+ raise ConfigTypeError("DictConfig in struct mode does not support pop")
+ if self._is_typed() and self._get_node_flag("struct") is not False:
+ raise ConfigTypeError(
+ f"{type_str(self._metadata.object_type)} (DictConfig) does not support pop"
+ )
+ key = self._validate_and_normalize_key(key)
+ node = self._get_child(key=key, validate_access=False)
+ if node is not None:
+ assert isinstance(node, Node)
+ value = self._resolve_with_default(
+ key=key, value=node, default_value=default
+ )
+
+ del self[key]
+ return value
+ else:
+ if default is not _DEFAULT_MARKER_:
+ return default
+ else:
+ full = self._get_full_key(key=key)
+ if full != key:
+ raise ConfigKeyError(
+ f"Key not found: '{key!s}' (path: '{full}')"
+ )
+ else:
+ raise ConfigKeyError(f"Key not found: '{key!s}'")
+ except Exception as e:
+ self._format_and_raise(key=key, value=None, cause=e)
+
+ def keys(self) -> KeysView[DictKeyType]:
+ if self._is_missing() or self._is_interpolation() or self._is_none():
+ return {}.keys()
+ ret = self.__dict__["_content"].keys()
+ assert isinstance(ret, KeysView)
+ return ret
+
+ def __contains__(self, key: object) -> bool:
+ """
+ A key is contained in a DictConfig if there is an associated value and
+ it is not a mandatory missing value ('???').
+ :param key:
+ :return:
+ """
+
+ try:
+ key = self._validate_and_normalize_key(key)
+ except KeyValidationError:
+ return False
+
+ try:
+ node = self._get_child(key)
+ assert node is None or isinstance(node, Node)
+ except (KeyError, AttributeError):
+ node = None
+
+ if node is None:
+ return False
+ else:
+ try:
+ self._resolve_with_default(key=key, value=node)
+ return True
+ except InterpolationResolutionError:
+ # Interpolations that fail count as existing.
+ return True
+ except MissingMandatoryValue:
+ # Missing values count as *not* existing.
+ return False
+
+ def __iter__(self) -> Iterator[DictKeyType]:
+ return iter(self.keys())
+
+ def items(self) -> ItemsView[DictKeyType, Any]:
+ return dict(self.items_ex(resolve=True, keys=None)).items()
+
+ def setdefault(self, key: DictKeyType, default: Any = None) -> Any:
+ if key in self:
+ ret = self.__getitem__(key)
+ else:
+ ret = default
+ self.__setitem__(key, default)
+ return ret
+
+ def items_ex(
+ self, resolve: bool = True, keys: Optional[Sequence[DictKeyType]] = None
+ ) -> List[Tuple[DictKeyType, Any]]:
+ items: List[Tuple[DictKeyType, Any]] = []
+
+ if self._is_none():
+ self._format_and_raise(
+ key=None,
+ value=None,
+ cause=TypeError("Cannot iterate a DictConfig object representing None"),
+ )
+ if self._is_missing():
+ raise MissingMandatoryValue("Cannot iterate a missing DictConfig")
+
+ for key in self.keys():
+ if resolve:
+ value = self[key]
+ else:
+ value = self.__dict__["_content"][key]
+ if isinstance(value, ValueNode):
+ value = value._value()
+ if keys is None or key in keys:
+ items.append((key, value))
+
+ return items
+
+ def __eq__(self, other: Any) -> bool:
+ if other is None:
+ return self.__dict__["_content"] is None
+ if is_primitive_dict(other) or is_structured_config(other):
+ other = DictConfig(other, flags={"allow_objects": True})
+ return DictConfig._dict_conf_eq(self, other)
+ if isinstance(other, DictConfig):
+ return DictConfig._dict_conf_eq(self, other)
+ if self._is_missing():
+ return _is_missing_literal(other)
+ return NotImplemented
+
+ def __ne__(self, other: Any) -> bool:
+ x = self.__eq__(other)
+ if x is not NotImplemented:
+ return not x
+ return NotImplemented
+
+ def __hash__(self) -> int:
+ return hash(str(self))
+
+ def _promote(self, type_or_prototype: Optional[Type[Any]]) -> None:
+ """
+ Retypes a node.
+ This should only be used in rare circumstances, where you want to dynamically change
+ the runtime structured-type of a DictConfig.
+ It will change the type and add the additional fields based on the input class or object
+ """
+ if type_or_prototype is None:
+ return
+ if not is_structured_config(type_or_prototype):
+ raise ValueError(f"Expected structured config class: {type_or_prototype}")
+
+ from omegaconf import OmegaConf
+
+ proto: DictConfig = OmegaConf.structured(type_or_prototype)
+ object_type = proto._metadata.object_type
+ # remove the type to prevent assignment validation from rejecting the promotion.
+ proto._metadata.object_type = None
+ self.merge_with(proto)
+ # restore the type.
+ self._metadata.object_type = object_type
+
+ def _set_value(self, value: Any, flags: Optional[Dict[str, bool]] = None) -> None:
+ try:
+ previous_content = self.__dict__["_content"]
+ self._set_value_impl(value, flags)
+ except Exception as e:
+ self.__dict__["_content"] = previous_content
+ raise e
+
+ def _set_value_impl(
+ self, value: Any, flags: Optional[Dict[str, bool]] = None
+ ) -> None:
+ from omegaconf import MISSING, flag_override
+
+ if flags is None:
+ flags = {}
+
+ assert not isinstance(value, ValueNode)
+ self._validate_set(key=None, value=value)
+
+ if _is_none(value, resolve=True):
+ self.__dict__["_content"] = None
+ self._metadata.object_type = None
+ elif _is_interpolation(value, strict_interpolation_validation=True):
+ self.__dict__["_content"] = value
+ self._metadata.object_type = None
+ elif _is_missing_value(value):
+ self.__dict__["_content"] = MISSING
+ self._metadata.object_type = None
+ else:
+ self.__dict__["_content"] = {}
+ if is_structured_config(value):
+ self._metadata.object_type = None
+ ao = self._get_flag("allow_objects")
+ data = get_structured_config_data(value, allow_objects=ao)
+ with flag_override(self, ["struct", "readonly"], False):
+ for k, v in data.items():
+ self.__setitem__(k, v)
+ self._metadata.object_type = get_type_of(value)
+
+ elif isinstance(value, DictConfig):
+ self._metadata.flags = copy.deepcopy(flags)
+ with flag_override(self, ["struct", "readonly"], False):
+ for k, v in value.__dict__["_content"].items():
+ self.__setitem__(k, v)
+ self._metadata.object_type = value._metadata.object_type
+
+ elif isinstance(value, dict):
+ with flag_override(self, ["struct", "readonly"], False):
+ for k, v in value.items():
+ self.__setitem__(k, v)
+ self._metadata.object_type = dict
+
+ else: # pragma: no cover
+ msg = f"Unsupported value type: {value}"
+ raise ValidationError(msg)
+
+ @staticmethod
+ def _dict_conf_eq(d1: "DictConfig", d2: "DictConfig") -> bool:
+
+ d1_none = d1.__dict__["_content"] is None
+ d2_none = d2.__dict__["_content"] is None
+ if d1_none and d2_none:
+ return True
+ if d1_none != d2_none:
+ return False
+
+ assert isinstance(d1, DictConfig)
+ assert isinstance(d2, DictConfig)
+ if len(d1) != len(d2):
+ return False
+ if d1._is_missing() or d2._is_missing():
+ return d1._is_missing() is d2._is_missing()
+
+ for k, v in d1.items_ex(resolve=False):
+ if k not in d2.__dict__["_content"]:
+ return False
+ if not BaseContainer._item_eq(d1, k, d2, k):
+ return False
+
+ return True
+
+ def _to_object(self) -> Any:
+ """
+ Instantiate an instance of `self._metadata.object_type`.
+ This requires `self` to be a structured config.
+ Nested subconfigs are converted by calling `OmegaConf.to_object`.
+ """
+ from omegaconf import OmegaConf
+
+ object_type = self._metadata.object_type
+ assert is_structured_config(object_type)
+ init_field_names = set(get_structured_config_init_field_names(object_type))
+
+ init_field_items: Dict[str, Any] = {}
+ non_init_field_items: Dict[str, Any] = {}
+ for k in self.keys():
+ assert isinstance(k, str)
+ node = self._get_child(k)
+ assert isinstance(node, Node)
+ try:
+ node = node._dereference_node()
+ except InterpolationResolutionError as e:
+ self._format_and_raise(key=k, value=None, cause=e)
+ if node._is_missing():
+ if k not in init_field_names:
+ continue # MISSING is ignored for init=False fields
+ self._format_and_raise(
+ key=k,
+ value=None,
+ cause=MissingMandatoryValue(
+ "Structured config of type `$OBJECT_TYPE` has missing mandatory value: $KEY"
+ ),
+ )
+ if isinstance(node, Container):
+ v = OmegaConf.to_object(node)
+ else:
+ v = node._value()
+
+ if k in init_field_names:
+ init_field_items[k] = v
+ else:
+ non_init_field_items[k] = v
+
+ try:
+ result = object_type(**init_field_items)
+ except TypeError as exc:
+ self._format_and_raise(
+ key=None,
+ value=None,
+ cause=exc,
+ msg="Could not create instance of `$OBJECT_TYPE`: " + str(exc),
+ )
+
+ for k, v in non_init_field_items.items():
+ setattr(result, k, v)
+ return result
diff --git a/cameras/deps/omegaconf/errors.py b/cameras/deps/omegaconf/errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..60525ef3c62fb89093c0b5aa1147ae6f80b61062
--- /dev/null
+++ b/cameras/deps/omegaconf/errors.py
@@ -0,0 +1,141 @@
+from typing import Any, Optional, Type
+
+
+class OmegaConfBaseException(Exception):
+ # would ideally be typed Optional[Node]
+ parent_node: Any
+ child_node: Any
+ key: Any
+ full_key: Optional[str]
+ value: Any
+ msg: Optional[str]
+ cause: Optional[Exception]
+ object_type: Optional[Type[Any]]
+ object_type_str: Optional[str]
+ ref_type: Optional[Type[Any]]
+ ref_type_str: Optional[str]
+
+ _initialized: bool = False
+
+ def __init__(self, *_args: Any, **_kwargs: Any) -> None:
+ self.parent_node = None
+ self.child_node = None
+ self.key = None
+ self.full_key = None
+ self.value = None
+ self.msg = None
+ self.object_type = None
+ self.ref_type = None
+
+
+class MissingMandatoryValue(OmegaConfBaseException):
+ """Thrown when a variable flagged with '???' value is accessed to
+ indicate that the value was not set"""
+
+
+class KeyValidationError(OmegaConfBaseException, ValueError):
+ """
+ Thrown when an a key of invalid type is used
+ """
+
+
+class ValidationError(OmegaConfBaseException, ValueError):
+ """
+ Thrown when a value fails validation
+ """
+
+
+class UnsupportedValueType(ValidationError, ValueError):
+ """
+ Thrown when an input value is not of supported type
+ """
+
+
+class ReadonlyConfigError(OmegaConfBaseException):
+ """
+ Thrown when someone tries to modify a frozen config
+ """
+
+
+class InterpolationResolutionError(OmegaConfBaseException, ValueError):
+ """
+ Base class for exceptions raised when resolving an interpolation.
+ """
+
+
+class UnsupportedInterpolationType(InterpolationResolutionError):
+ """
+ Thrown when an attempt to use an unregistered interpolation is made
+ """
+
+
+class InterpolationKeyError(InterpolationResolutionError):
+ """
+ Thrown when a node does not exist when resolving an interpolation.
+ """
+
+
+class InterpolationToMissingValueError(InterpolationResolutionError):
+ """
+ Thrown when a node interpolation points to a node that is set to ???.
+ """
+
+
+class InterpolationValidationError(InterpolationResolutionError, ValidationError):
+ """
+ Thrown when the result of an interpolation fails the validation step.
+ """
+
+
+class ConfigKeyError(OmegaConfBaseException, KeyError):
+ """
+ Thrown from DictConfig when a regular dict access would have caused a KeyError.
+ """
+
+ msg: str
+
+ def __init__(self, msg: str) -> None:
+ super().__init__(msg)
+ self.msg = msg
+
+ def __str__(self) -> str:
+ """
+ Workaround to nasty KeyError quirk: https://bugs.python.org/issue2651
+ """
+ return self.msg
+
+
+class ConfigAttributeError(OmegaConfBaseException, AttributeError):
+ """
+ Thrown from a config object when a regular access would have caused an AttributeError.
+ """
+
+
+class ConfigTypeError(OmegaConfBaseException, TypeError):
+ """
+ Thrown from a config object when a regular access would have caused a TypeError.
+ """
+
+
+class ConfigIndexError(OmegaConfBaseException, IndexError):
+ """
+ Thrown from a config object when a regular access would have caused an IndexError.
+ """
+
+
+class ConfigValueError(OmegaConfBaseException, ValueError):
+ """
+ Thrown from a config object when a regular access would have caused a ValueError.
+ """
+
+
+class ConfigCycleDetectedException(OmegaConfBaseException):
+ """
+ Thrown when a cycle is detected in the graph made by config nodes.
+ """
+
+
+class GrammarParseError(OmegaConfBaseException):
+ """
+ Thrown when failing to parse an expression according to the ANTLR grammar.
+ """
diff --git a/cameras/deps/omegaconf/grammar/__init__.py b/cameras/deps/omegaconf/grammar/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarLexer.py b/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarLexer.py
new file mode 100644
index 0000000000000000000000000000000000000000..1bf9416fab12aaf7eb45c73b620037d4800e3496
--- /dev/null
+++ b/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarLexer.py
@@ -0,0 +1,337 @@
+# Generated from /tmp/build-via-sdist-fm63w174/omegaconf-2.3.0/omegaconf/grammar/OmegaConfGrammarLexer.g4 by ANTLR 4.9.3
+from antlr4 import *
+from io import StringIO
+import sys
+if sys.version_info[1] > 5:
+ from typing import TextIO
+else:
+ from typing.io import TextIO
+
+
+
+def serializedATN():
+ with StringIO() as buf:
+ buf.write("\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2\36")
+ buf.write("\u01e7\b\1\b\1\b\1\b\1\b\1\4\2\t\2\4\3\t\3\4\4\t\4\4\5")
+ buf.write("\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13")
+ buf.write("\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t")
+ buf.write("\21\4\22\t\22\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26")
+ buf.write("\4\27\t\27\4\30\t\30\4\31\t\31\4\32\t\32\4\33\t\33\4\34")
+ buf.write("\t\34\4\35\t\35\4\36\t\36\4\37\t\37\4 \t \4!\t!\4\"\t")
+ buf.write("\"\4#\t#\4$\t$\4%\t%\4&\t&\4\'\t\'\4(\t(\4)\t)\4*\t*\4")
+ buf.write("+\t+\4,\t,\4-\t-\4.\t.\4/\t/\4\60\t\60\4\61\t\61\4\62")
+ buf.write("\t\62\4\63\t\63\4\64\t\64\4\65\t\65\4\66\t\66\3\2\3\2")
+ buf.write("\3\3\3\3\3\4\3\4\3\4\5\4y\n\4\3\4\7\4|\n\4\f\4\16\4\177")
+ buf.write("\13\4\5\4\u0081\n\4\3\5\3\5\3\5\3\6\3\6\3\6\3\6\3\6\3")
+ buf.write("\7\7\7\u008c\n\7\f\7\16\7\u008f\13\7\3\7\3\7\3\b\7\b\u0094")
+ buf.write("\n\b\f\b\16\b\u0097\13\b\3\b\3\b\3\b\3\b\3\t\6\t\u009e")
+ buf.write("\n\t\r\t\16\t\u009f\3\n\6\n\u00a3\n\n\r\n\16\n\u00a4\3")
+ buf.write("\n\3\n\3\13\3\13\3\13\3\13\3\f\3\f\3\f\3\f\5\f\u00b1\n")
+ buf.write("\f\3\f\3\f\3\r\3\r\5\r\u00b7\n\r\3\r\3\r\3\16\5\16\u00bc")
+ buf.write("\n\16\3\16\3\16\3\16\3\16\3\17\3\17\3\17\3\17\3\20\3\20")
+ buf.write("\3\20\3\20\3\21\5\21\u00cb\n\21\3\21\3\21\5\21\u00cf\n")
+ buf.write("\21\3\22\3\22\5\22\u00d3\n\22\3\23\5\23\u00d6\n\23\3\23")
+ buf.write("\3\23\3\24\5\24\u00db\n\24\3\24\3\24\5\24\u00df\n\24\3")
+ buf.write("\25\3\25\3\25\3\25\5\25\u00e5\n\25\3\25\3\25\3\25\5\25")
+ buf.write("\u00ea\n\25\3\25\7\25\u00ed\n\25\f\25\16\25\u00f0\13\25")
+ buf.write("\5\25\u00f2\n\25\3\26\3\26\5\26\u00f6\n\26\3\26\3\26\5")
+ buf.write("\26\u00fa\n\26\3\26\3\26\5\26\u00fe\n\26\3\26\7\26\u0101")
+ buf.write("\n\26\f\26\16\26\u0104\13\26\3\27\5\27\u0107\n\27\3\27")
+ buf.write("\3\27\3\27\3\27\3\27\3\27\3\27\3\27\5\27\u0111\n\27\3")
+ buf.write("\30\5\30\u0114\n\30\3\30\3\30\3\31\3\31\3\31\3\31\3\31")
+ buf.write("\3\31\3\31\3\31\3\31\5\31\u0121\n\31\3\32\3\32\3\32\3")
+ buf.write("\32\3\32\3\33\3\33\3\34\3\34\5\34\u012c\n\34\3\34\3\34")
+ buf.write("\3\34\7\34\u0131\n\34\f\34\16\34\u0134\13\34\3\35\3\35")
+ buf.write("\3\35\3\35\3\35\3\35\3\35\3\35\3\35\3\35\3\35\3\35\3\35")
+ buf.write("\3\35\3\35\3\35\3\35\3\35\3\35\3\35\3\35\3\35\3\35\6\35")
+ buf.write("\u014d\n\35\r\35\16\35\u014e\3\36\6\36\u0152\n\36\r\36")
+ buf.write("\16\36\u0153\3\37\3\37\5\37\u0158\n\37\3\37\3\37\3\37")
+ buf.write("\3 \5 \u015e\n \3 \3 \5 \u0162\n \3 \3 \3 \3!\5!\u0168")
+ buf.write("\n!\3!\3!\3!\3!\3\"\3\"\3#\3#\3#\3#\3$\3$\3$\3$\3%\3%")
+ buf.write("\3%\3%\3&\6&\u017d\n&\r&\16&\u017e\3\'\3\'\3\'\3\'\3\'")
+ buf.write("\3(\3(\3(\3(\3)\7)\u018b\n)\f)\16)\u018e\13)\3)\3)\3)")
+ buf.write("\3)\3*\3*\3*\3*\3+\7+\u0199\n+\f+\16+\u019c\13+\3+\3+")
+ buf.write("\3+\3+\3+\3,\6,\u01a4\n,\r,\16,\u01a5\3-\6-\u01a9\n-\r")
+ buf.write("-\16-\u01aa\3-\3-\3.\3.\3.\3.\3/\3/\3/\3/\3/\3\60\3\60")
+ buf.write("\3\60\3\60\3\60\3\61\7\61\u01be\n\61\f\61\16\61\u01c1")
+ buf.write("\13\61\3\61\3\61\3\61\3\61\3\62\3\62\3\62\3\62\3\63\7")
+ buf.write("\63\u01cc\n\63\f\63\16\63\u01cf\13\63\3\63\3\63\3\63\3")
+ buf.write("\63\3\63\3\64\6\64\u01d7\n\64\r\64\16\64\u01d8\3\64\3")
+ buf.write("\64\3\65\6\65\u01de\n\65\r\65\16\65\u01df\3\65\3\65\3")
+ buf.write("\66\3\66\3\66\3\66\2\2\67\7\2\t\2\13\2\r\2\17\2\21\3\23")
+ buf.write("\4\25\5\27\2\31\34\33\6\35\7\37\b!\t#\n%\13\'\f)\r+\16")
+ buf.write("-\2/\2\61\17\63\20\65\21\67\229\23;\24=\25?\26A\2C\2E")
+ buf.write("\27G\30I\35K\36M\2O\31Q\2S\32U\2W\2Y\2[\33]\2_\2a\2c\2")
+ buf.write("e\2g\2i\2k\2m\2o\2\7\2\3\4\5\6\32\4\2C\\c|\3\2\62;\3\2")
+ buf.write("\63;\3\2&&\4\2&&^^\4\2GGgg\4\2--//\4\2KKkk\4\2PPpp\4\2")
+ buf.write("HHhh\4\2CCcc\4\2VVvv\4\2TTtt\4\2WWww\4\2NNnn\4\2UUuu\b")
+ buf.write("\2&\',-/\61AB^^~~\4\2//aa\4\2\13\13\"\"\13\2\13\13\"\"")
+ buf.write("$$)+\60\60<<]_}}\177\177\4\2&&))\5\2&&))^^\4\2$$&&\5\2")
+ buf.write("$$&&^^\2\u0218\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2")
+ buf.write("\2\25\3\2\2\2\2\27\3\2\2\2\2\31\3\2\2\2\3\33\3\2\2\2\3")
+ buf.write("\35\3\2\2\2\3\37\3\2\2\2\3!\3\2\2\2\3#\3\2\2\2\3%\3\2")
+ buf.write("\2\2\3\'\3\2\2\2\3)\3\2\2\2\3+\3\2\2\2\3\61\3\2\2\2\3")
+ buf.write("\63\3\2\2\2\3\65\3\2\2\2\3\67\3\2\2\2\39\3\2\2\2\3;\3")
+ buf.write("\2\2\2\3=\3\2\2\2\3?\3\2\2\2\4A\3\2\2\2\4C\3\2\2\2\4E")
+ buf.write("\3\2\2\2\4G\3\2\2\2\4I\3\2\2\2\4K\3\2\2\2\4M\3\2\2\2\4")
+ buf.write("O\3\2\2\2\5Q\3\2\2\2\5S\3\2\2\2\5U\3\2\2\2\5W\3\2\2\2")
+ buf.write("\5Y\3\2\2\2\5[\3\2\2\2\5]\3\2\2\2\5_\3\2\2\2\6a\3\2\2")
+ buf.write("\2\6c\3\2\2\2\6e\3\2\2\2\6g\3\2\2\2\6i\3\2\2\2\6k\3\2")
+ buf.write("\2\2\6m\3\2\2\2\6o\3\2\2\2\7q\3\2\2\2\ts\3\2\2\2\13\u0080")
+ buf.write("\3\2\2\2\r\u0082\3\2\2\2\17\u0085\3\2\2\2\21\u008d\3\2")
+ buf.write("\2\2\23\u0095\3\2\2\2\25\u009d\3\2\2\2\27\u00a2\3\2\2")
+ buf.write("\2\31\u00a8\3\2\2\2\33\u00ac\3\2\2\2\35\u00b4\3\2\2\2")
+ buf.write("\37\u00bb\3\2\2\2!\u00c1\3\2\2\2#\u00c5\3\2\2\2%\u00ca")
+ buf.write("\3\2\2\2\'\u00d0\3\2\2\2)\u00d5\3\2\2\2+\u00da\3\2\2\2")
+ buf.write("-\u00f1\3\2\2\2/\u00f5\3\2\2\2\61\u0106\3\2\2\2\63\u0113")
+ buf.write("\3\2\2\2\65\u0120\3\2\2\2\67\u0122\3\2\2\29\u0127\3\2")
+ buf.write("\2\2;\u012b\3\2\2\2=\u014c\3\2\2\2?\u0151\3\2\2\2A\u0155")
+ buf.write("\3\2\2\2C\u015d\3\2\2\2E\u0167\3\2\2\2G\u016d\3\2\2\2")
+ buf.write("I\u016f\3\2\2\2K\u0173\3\2\2\2M\u0177\3\2\2\2O\u017c\3")
+ buf.write("\2\2\2Q\u0180\3\2\2\2S\u0185\3\2\2\2U\u018c\3\2\2\2W\u0193")
+ buf.write("\3\2\2\2Y\u019a\3\2\2\2[\u01a3\3\2\2\2]\u01a8\3\2\2\2")
+ buf.write("_\u01ae\3\2\2\2a\u01b2\3\2\2\2c\u01b7\3\2\2\2e\u01bf\3")
+ buf.write("\2\2\2g\u01c6\3\2\2\2i\u01cd\3\2\2\2k\u01d6\3\2\2\2m\u01dd")
+ buf.write("\3\2\2\2o\u01e3\3\2\2\2qr\t\2\2\2r\b\3\2\2\2st\t\3\2\2")
+ buf.write("t\n\3\2\2\2u\u0081\7\62\2\2v}\t\4\2\2wy\7a\2\2xw\3\2\2")
+ buf.write("\2xy\3\2\2\2yz\3\2\2\2z|\5\t\3\2{x\3\2\2\2|\177\3\2\2")
+ buf.write("\2}{\3\2\2\2}~\3\2\2\2~\u0081\3\2\2\2\177}\3\2\2\2\u0080")
+ buf.write("u\3\2\2\2\u0080v\3\2\2\2\u0081\f\3\2\2\2\u0082\u0083\7")
+ buf.write("^\2\2\u0083\u0084\7^\2\2\u0084\16\3\2\2\2\u0085\u0086")
+ buf.write("\5\33\f\2\u0086\u0087\3\2\2\2\u0087\u0088\b\6\2\2\u0088")
+ buf.write("\u0089\b\6\3\2\u0089\20\3\2\2\2\u008a\u008c\n\5\2\2\u008b")
+ buf.write("\u008a\3\2\2\2\u008c\u008f\3\2\2\2\u008d\u008b\3\2\2\2")
+ buf.write("\u008d\u008e\3\2\2\2\u008e\u0090\3\2\2\2\u008f\u008d\3")
+ buf.write("\2\2\2\u0090\u0091\n\6\2\2\u0091\22\3\2\2\2\u0092\u0094")
+ buf.write("\5\r\5\2\u0093\u0092\3\2\2\2\u0094\u0097\3\2\2\2\u0095")
+ buf.write("\u0093\3\2\2\2\u0095\u0096\3\2\2\2\u0096\u0098\3\2\2\2")
+ buf.write("\u0097\u0095\3\2\2\2\u0098\u0099\7^\2\2\u0099\u009a\7")
+ buf.write("&\2\2\u009a\u009b\7}\2\2\u009b\24\3\2\2\2\u009c\u009e")
+ buf.write("\5\r\5\2\u009d\u009c\3\2\2\2\u009e\u009f\3\2\2\2\u009f")
+ buf.write("\u009d\3\2\2\2\u009f\u00a0\3\2\2\2\u00a0\26\3\2\2\2\u00a1")
+ buf.write("\u00a3\7^\2\2\u00a2\u00a1\3\2\2\2\u00a3\u00a4\3\2\2\2")
+ buf.write("\u00a4\u00a2\3\2\2\2\u00a4\u00a5\3\2\2\2\u00a5\u00a6\3")
+ buf.write("\2\2\2\u00a6\u00a7\b\n\4\2\u00a7\30\3\2\2\2\u00a8\u00a9")
+ buf.write("\7&\2\2\u00a9\u00aa\3\2\2\2\u00aa\u00ab\b\13\4\2\u00ab")
+ buf.write("\32\3\2\2\2\u00ac\u00ad\7&\2\2\u00ad\u00ae\7}\2\2\u00ae")
+ buf.write("\u00b0\3\2\2\2\u00af\u00b1\5?\36\2\u00b0\u00af\3\2\2\2")
+ buf.write("\u00b0\u00b1\3\2\2\2\u00b1\u00b2\3\2\2\2\u00b2\u00b3\b")
+ buf.write("\f\3\2\u00b3\34\3\2\2\2\u00b4\u00b6\7}\2\2\u00b5\u00b7")
+ buf.write("\5?\36\2\u00b6\u00b5\3\2\2\2\u00b6\u00b7\3\2\2\2\u00b7")
+ buf.write("\u00b8\3\2\2\2\u00b8\u00b9\b\r\5\2\u00b9\36\3\2\2\2\u00ba")
+ buf.write("\u00bc\5?\36\2\u00bb\u00ba\3\2\2\2\u00bb\u00bc\3\2\2\2")
+ buf.write("\u00bc\u00bd\3\2\2\2\u00bd\u00be\7\177\2\2\u00be\u00bf")
+ buf.write("\3\2\2\2\u00bf\u00c0\b\16\6\2\u00c0 \3\2\2\2\u00c1\u00c2")
+ buf.write("\7)\2\2\u00c2\u00c3\3\2\2\2\u00c3\u00c4\b\17\7\2\u00c4")
+ buf.write("\"\3\2\2\2\u00c5\u00c6\7$\2\2\u00c6\u00c7\3\2\2\2\u00c7")
+ buf.write("\u00c8\b\20\b\2\u00c8$\3\2\2\2\u00c9\u00cb\5?\36\2\u00ca")
+ buf.write("\u00c9\3\2\2\2\u00ca\u00cb\3\2\2\2\u00cb\u00cc\3\2\2\2")
+ buf.write("\u00cc\u00ce\7.\2\2\u00cd\u00cf\5?\36\2\u00ce\u00cd\3")
+ buf.write("\2\2\2\u00ce\u00cf\3\2\2\2\u00cf&\3\2\2\2\u00d0\u00d2")
+ buf.write("\7]\2\2\u00d1\u00d3\5?\36\2\u00d2\u00d1\3\2\2\2\u00d2")
+ buf.write("\u00d3\3\2\2\2\u00d3(\3\2\2\2\u00d4\u00d6\5?\36\2\u00d5")
+ buf.write("\u00d4\3\2\2\2\u00d5\u00d6\3\2\2\2\u00d6\u00d7\3\2\2\2")
+ buf.write("\u00d7\u00d8\7_\2\2\u00d8*\3\2\2\2\u00d9\u00db\5?\36\2")
+ buf.write("\u00da\u00d9\3\2\2\2\u00da\u00db\3\2\2\2\u00db\u00dc\3")
+ buf.write("\2\2\2\u00dc\u00de\7<\2\2\u00dd\u00df\5?\36\2\u00de\u00dd")
+ buf.write("\3\2\2\2\u00de\u00df\3\2\2\2\u00df,\3\2\2\2\u00e0\u00e1")
+ buf.write("\5\13\4\2\u00e1\u00e2\7\60\2\2\u00e2\u00f2\3\2\2\2\u00e3")
+ buf.write("\u00e5\5\13\4\2\u00e4\u00e3\3\2\2\2\u00e4\u00e5\3\2\2")
+ buf.write("\2\u00e5\u00e6\3\2\2\2\u00e6\u00e7\7\60\2\2\u00e7\u00ee")
+ buf.write("\5\t\3\2\u00e8\u00ea\7a\2\2\u00e9\u00e8\3\2\2\2\u00e9")
+ buf.write("\u00ea\3\2\2\2\u00ea\u00eb\3\2\2\2\u00eb\u00ed\5\t\3\2")
+ buf.write("\u00ec\u00e9\3\2\2\2\u00ed\u00f0\3\2\2\2\u00ee\u00ec\3")
+ buf.write("\2\2\2\u00ee\u00ef\3\2\2\2\u00ef\u00f2\3\2\2\2\u00f0\u00ee")
+ buf.write("\3\2\2\2\u00f1\u00e0\3\2\2\2\u00f1\u00e4\3\2\2\2\u00f2")
+ buf.write(".\3\2\2\2\u00f3\u00f6\5\13\4\2\u00f4\u00f6\5-\25\2\u00f5")
+ buf.write("\u00f3\3\2\2\2\u00f5\u00f4\3\2\2\2\u00f6\u00f7\3\2\2\2")
+ buf.write("\u00f7\u00f9\t\7\2\2\u00f8\u00fa\t\b\2\2\u00f9\u00f8\3")
+ buf.write("\2\2\2\u00f9\u00fa\3\2\2\2\u00fa\u00fb\3\2\2\2\u00fb\u0102")
+ buf.write("\5\t\3\2\u00fc\u00fe\7a\2\2\u00fd\u00fc\3\2\2\2\u00fd")
+ buf.write("\u00fe\3\2\2\2\u00fe\u00ff\3\2\2\2\u00ff\u0101\5\t\3\2")
+ buf.write("\u0100\u00fd\3\2\2\2\u0101\u0104\3\2\2\2\u0102\u0100\3")
+ buf.write("\2\2\2\u0102\u0103\3\2\2\2\u0103\60\3\2\2\2\u0104\u0102")
+ buf.write("\3\2\2\2\u0105\u0107\t\b\2\2\u0106\u0105\3\2\2\2\u0106")
+ buf.write("\u0107\3\2\2\2\u0107\u0110\3\2\2\2\u0108\u0111\5-\25\2")
+ buf.write("\u0109\u0111\5/\26\2\u010a\u010b\t\t\2\2\u010b\u010c\t")
+ buf.write("\n\2\2\u010c\u0111\t\13\2\2\u010d\u010e\t\n\2\2\u010e")
+ buf.write("\u010f\t\f\2\2\u010f\u0111\t\n\2\2\u0110\u0108\3\2\2\2")
+ buf.write("\u0110\u0109\3\2\2\2\u0110\u010a\3\2\2\2\u0110\u010d\3")
+ buf.write("\2\2\2\u0111\62\3\2\2\2\u0112\u0114\t\b\2\2\u0113\u0112")
+ buf.write("\3\2\2\2\u0113\u0114\3\2\2\2\u0114\u0115\3\2\2\2\u0115")
+ buf.write("\u0116\5\13\4\2\u0116\64\3\2\2\2\u0117\u0118\t\r\2\2\u0118")
+ buf.write("\u0119\t\16\2\2\u0119\u011a\t\17\2\2\u011a\u0121\t\7\2")
+ buf.write("\2\u011b\u011c\t\13\2\2\u011c\u011d\t\f\2\2\u011d\u011e")
+ buf.write("\t\20\2\2\u011e\u011f\t\21\2\2\u011f\u0121\t\7\2\2\u0120")
+ buf.write("\u0117\3\2\2\2\u0120\u011b\3\2\2\2\u0121\66\3\2\2\2\u0122")
+ buf.write("\u0123\t\n\2\2\u0123\u0124\t\17\2\2\u0124\u0125\t\20\2")
+ buf.write("\2\u0125\u0126\t\20\2\2\u01268\3\2\2\2\u0127\u0128\t\22")
+ buf.write("\2\2\u0128:\3\2\2\2\u0129\u012c\5\7\2\2\u012a\u012c\7")
+ buf.write("a\2\2\u012b\u0129\3\2\2\2\u012b\u012a\3\2\2\2\u012c\u0132")
+ buf.write("\3\2\2\2\u012d\u0131\5\7\2\2\u012e\u0131\5\t\3\2\u012f")
+ buf.write("\u0131\t\23\2\2\u0130\u012d\3\2\2\2\u0130\u012e\3\2\2")
+ buf.write("\2\u0130\u012f\3\2\2\2\u0131\u0134\3\2\2\2\u0132\u0130")
+ buf.write("\3\2\2\2\u0132\u0133\3\2\2\2\u0133<\3\2\2\2\u0134\u0132")
+ buf.write("\3\2\2\2\u0135\u014d\5\r\5\2\u0136\u0137\7^\2\2\u0137")
+ buf.write("\u014d\7*\2\2\u0138\u0139\7^\2\2\u0139\u014d\7+\2\2\u013a")
+ buf.write("\u013b\7^\2\2\u013b\u014d\7]\2\2\u013c\u013d\7^\2\2\u013d")
+ buf.write("\u014d\7_\2\2\u013e\u013f\7^\2\2\u013f\u014d\7}\2\2\u0140")
+ buf.write("\u0141\7^\2\2\u0141\u014d\7\177\2\2\u0142\u0143\7^\2\2")
+ buf.write("\u0143\u014d\7<\2\2\u0144\u0145\7^\2\2\u0145\u014d\7?")
+ buf.write("\2\2\u0146\u0147\7^\2\2\u0147\u014d\7.\2\2\u0148\u0149")
+ buf.write("\7^\2\2\u0149\u014d\7\"\2\2\u014a\u014b\7^\2\2\u014b\u014d")
+ buf.write("\7\13\2\2\u014c\u0135\3\2\2\2\u014c\u0136\3\2\2\2\u014c")
+ buf.write("\u0138\3\2\2\2\u014c\u013a\3\2\2\2\u014c\u013c\3\2\2\2")
+ buf.write("\u014c\u013e\3\2\2\2\u014c\u0140\3\2\2\2\u014c\u0142\3")
+ buf.write("\2\2\2\u014c\u0144\3\2\2\2\u014c\u0146\3\2\2\2\u014c\u0148")
+ buf.write("\3\2\2\2\u014c\u014a\3\2\2\2\u014d\u014e\3\2\2\2\u014e")
+ buf.write("\u014c\3\2\2\2\u014e\u014f\3\2\2\2\u014f>\3\2\2\2\u0150")
+ buf.write("\u0152\t\24\2\2\u0151\u0150\3\2\2\2\u0152\u0153\3\2\2")
+ buf.write("\2\u0153\u0151\3\2\2\2\u0153\u0154\3\2\2\2\u0154@\3\2")
+ buf.write("\2\2\u0155\u0157\5\33\f\2\u0156\u0158\5?\36\2\u0157\u0156")
+ buf.write("\3\2\2\2\u0157\u0158\3\2\2\2\u0158\u0159\3\2\2\2\u0159")
+ buf.write("\u015a\b\37\2\2\u015a\u015b\b\37\3\2\u015bB\3\2\2\2\u015c")
+ buf.write("\u015e\5?\36\2\u015d\u015c\3\2\2\2\u015d\u015e\3\2\2\2")
+ buf.write("\u015e\u015f\3\2\2\2\u015f\u0161\7<\2\2\u0160\u0162\5")
+ buf.write("?\36\2\u0161\u0160\3\2\2\2\u0161\u0162\3\2\2\2\u0162\u0163")
+ buf.write("\3\2\2\2\u0163\u0164\b \t\2\u0164\u0165\b \n\2\u0165D")
+ buf.write("\3\2\2\2\u0166\u0168\5?\36\2\u0167\u0166\3\2\2\2\u0167")
+ buf.write("\u0168\3\2\2\2\u0168\u0169\3\2\2\2\u0169\u016a\7\177\2")
+ buf.write("\2\u016a\u016b\3\2\2\2\u016b\u016c\b!\6\2\u016cF\3\2\2")
+ buf.write("\2\u016d\u016e\7\60\2\2\u016eH\3\2\2\2\u016f\u0170\7]")
+ buf.write("\2\2\u0170\u0171\3\2\2\2\u0171\u0172\b#\13\2\u0172J\3")
+ buf.write("\2\2\2\u0173\u0174\7_\2\2\u0174\u0175\3\2\2\2\u0175\u0176")
+ buf.write("\b$\f\2\u0176L\3\2\2\2\u0177\u0178\5;\34\2\u0178\u0179")
+ buf.write("\3\2\2\2\u0179\u017a\b%\r\2\u017aN\3\2\2\2\u017b\u017d")
+ buf.write("\n\25\2\2\u017c\u017b\3\2\2\2\u017d\u017e\3\2\2\2\u017e")
+ buf.write("\u017c\3\2\2\2\u017e\u017f\3\2\2\2\u017fP\3\2\2\2\u0180")
+ buf.write("\u0181\5\33\f\2\u0181\u0182\3\2\2\2\u0182\u0183\b\'\2")
+ buf.write("\2\u0183\u0184\b\'\3\2\u0184R\3\2\2\2\u0185\u0186\7)\2")
+ buf.write("\2\u0186\u0187\3\2\2\2\u0187\u0188\b(\6\2\u0188T\3\2\2")
+ buf.write("\2\u0189\u018b\n\26\2\2\u018a\u0189\3\2\2\2\u018b\u018e")
+ buf.write("\3\2\2\2\u018c\u018a\3\2\2\2\u018c\u018d\3\2\2\2\u018d")
+ buf.write("\u018f\3\2\2\2\u018e\u018c\3\2\2\2\u018f\u0190\n\27\2")
+ buf.write("\2\u0190\u0191\3\2\2\2\u0191\u0192\b)\4\2\u0192V\3\2\2")
+ buf.write("\2\u0193\u0194\5\23\b\2\u0194\u0195\3\2\2\2\u0195\u0196")
+ buf.write("\b*\16\2\u0196X\3\2\2\2\u0197\u0199\5\r\5\2\u0198\u0197")
+ buf.write("\3\2\2\2\u0199\u019c\3\2\2\2\u019a\u0198\3\2\2\2\u019a")
+ buf.write("\u019b\3\2\2\2\u019b\u019d\3\2\2\2\u019c\u019a\3\2\2\2")
+ buf.write("\u019d\u019e\7^\2\2\u019e\u019f\7)\2\2\u019f\u01a0\3\2")
+ buf.write("\2\2\u01a0\u01a1\b+\17\2\u01a1Z\3\2\2\2\u01a2\u01a4\5")
+ buf.write("\r\5\2\u01a3\u01a2\3\2\2\2\u01a4\u01a5\3\2\2\2\u01a5\u01a3")
+ buf.write("\3\2\2\2\u01a5\u01a6\3\2\2\2\u01a6\\\3\2\2\2\u01a7\u01a9")
+ buf.write("\7^\2\2\u01a8\u01a7\3\2\2\2\u01a9\u01aa\3\2\2\2\u01aa")
+ buf.write("\u01a8\3\2\2\2\u01aa\u01ab\3\2\2\2\u01ab\u01ac\3\2\2\2")
+ buf.write("\u01ac\u01ad\b-\4\2\u01ad^\3\2\2\2\u01ae\u01af\7&\2\2")
+ buf.write("\u01af\u01b0\3\2\2\2\u01b0\u01b1\b.\4\2\u01b1`\3\2\2\2")
+ buf.write("\u01b2\u01b3\5\33\f\2\u01b3\u01b4\3\2\2\2\u01b4\u01b5")
+ buf.write("\b/\2\2\u01b5\u01b6\b/\3\2\u01b6b\3\2\2\2\u01b7\u01b8")
+ buf.write("\7$\2\2\u01b8\u01b9\3\2\2\2\u01b9\u01ba\b\60\20\2\u01ba")
+ buf.write("\u01bb\b\60\6\2\u01bbd\3\2\2\2\u01bc\u01be\n\30\2\2\u01bd")
+ buf.write("\u01bc\3\2\2\2\u01be\u01c1\3\2\2\2\u01bf\u01bd\3\2\2\2")
+ buf.write("\u01bf\u01c0\3\2\2\2\u01c0\u01c2\3\2\2\2\u01c1\u01bf\3")
+ buf.write("\2\2\2\u01c2\u01c3\n\31\2\2\u01c3\u01c4\3\2\2\2\u01c4")
+ buf.write("\u01c5\b\61\4\2\u01c5f\3\2\2\2\u01c6\u01c7\5\23\b\2\u01c7")
+ buf.write("\u01c8\3\2\2\2\u01c8\u01c9\b\62\16\2\u01c9h\3\2\2\2\u01ca")
+ buf.write("\u01cc\5\r\5\2\u01cb\u01ca\3\2\2\2\u01cc\u01cf\3\2\2\2")
+ buf.write("\u01cd\u01cb\3\2\2\2\u01cd\u01ce\3\2\2\2\u01ce\u01d0\3")
+ buf.write("\2\2\2\u01cf\u01cd\3\2\2\2\u01d0\u01d1\7^\2\2\u01d1\u01d2")
+ buf.write("\7$\2\2\u01d2\u01d3\3\2\2\2\u01d3\u01d4\b\63\17\2\u01d4")
+ buf.write("j\3\2\2\2\u01d5\u01d7\5\r\5\2\u01d6\u01d5\3\2\2\2\u01d7")
+ buf.write("\u01d8\3\2\2\2\u01d8\u01d6\3\2\2\2\u01d8\u01d9\3\2\2\2")
+ buf.write("\u01d9\u01da\3\2\2\2\u01da\u01db\b\64\21\2\u01dbl\3\2")
+ buf.write("\2\2\u01dc\u01de\7^\2\2\u01dd\u01dc\3\2\2\2\u01de\u01df")
+ buf.write("\3\2\2\2\u01df\u01dd\3\2\2\2\u01df\u01e0\3\2\2\2\u01e0")
+ buf.write("\u01e1\3\2\2\2\u01e1\u01e2\b\65\4\2\u01e2n\3\2\2\2\u01e3")
+ buf.write("\u01e4\7&\2\2\u01e4\u01e5\3\2\2\2\u01e5\u01e6\b\66\4\2")
+ buf.write("\u01e6p\3\2\2\2\66\2\3\4\5\6x}\u0080\u008d\u0095\u009f")
+ buf.write("\u00a4\u00b0\u00b6\u00bb\u00ca\u00ce\u00d2\u00d5\u00da")
+ buf.write("\u00de\u00e4\u00e9\u00ee\u00f1\u00f5\u00f9\u00fd\u0102")
+ buf.write("\u0106\u0110\u0113\u0120\u012b\u0130\u0132\u014c\u014e")
+ buf.write("\u0153\u0157\u015d\u0161\u0167\u017e\u018c\u019a\u01a5")
+ buf.write("\u01aa\u01bf\u01cd\u01d8\u01df\22\t\6\2\7\4\2\t\3\2\7")
+ buf.write("\3\2\6\2\2\7\5\2\7\6\2\t\16\2\4\3\2\t\f\2\t\r\2\t\24\2")
+ buf.write("\t\4\2\t\25\2\t\32\2\t\33\2")
+ return buf.getvalue()
+
+
+class OmegaConfGrammarLexer(Lexer):
+
+ atn = ATNDeserializer().deserialize(serializedATN())
+
+ decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
+
+ VALUE_MODE = 1
+ INTERPOLATION_MODE = 2
+ QUOTED_SINGLE_MODE = 3
+ QUOTED_DOUBLE_MODE = 4
+
+ ANY_STR = 1
+ ESC_INTER = 2
+ TOP_ESC = 3
+ INTER_OPEN = 4
+ BRACE_OPEN = 5
+ BRACE_CLOSE = 6
+ QUOTE_OPEN_SINGLE = 7
+ QUOTE_OPEN_DOUBLE = 8
+ COMMA = 9
+ BRACKET_OPEN = 10
+ BRACKET_CLOSE = 11
+ COLON = 12
+ FLOAT = 13
+ INT = 14
+ BOOL = 15
+ NULL = 16
+ UNQUOTED_CHAR = 17
+ ID = 18
+ ESC = 19
+ WS = 20
+ INTER_CLOSE = 21
+ DOT = 22
+ INTER_KEY = 23
+ MATCHING_QUOTE_CLOSE = 24
+ QUOTED_ESC = 25
+ DOLLAR = 26
+ INTER_BRACKET_OPEN = 27
+ INTER_BRACKET_CLOSE = 28
+
+ channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ]
+
+ modeNames = [ "DEFAULT_MODE", "VALUE_MODE", "INTERPOLATION_MODE", "QUOTED_SINGLE_MODE",
+ "QUOTED_DOUBLE_MODE" ]
+
+ literalNames = [ "",
+ "'.'", "'['", "']'" ]
+
+ symbolicNames = [ "",
+ "ANY_STR", "ESC_INTER", "TOP_ESC", "INTER_OPEN", "BRACE_OPEN",
+ "BRACE_CLOSE", "QUOTE_OPEN_SINGLE", "QUOTE_OPEN_DOUBLE", "COMMA",
+ "BRACKET_OPEN", "BRACKET_CLOSE", "COLON", "FLOAT", "INT", "BOOL",
+ "NULL", "UNQUOTED_CHAR", "ID", "ESC", "WS", "INTER_CLOSE", "DOT",
+ "INTER_KEY", "MATCHING_QUOTE_CLOSE", "QUOTED_ESC", "DOLLAR",
+ "INTER_BRACKET_OPEN", "INTER_BRACKET_CLOSE" ]
+
+ ruleNames = [ "CHAR", "DIGIT", "INT_UNSIGNED", "ESC_BACKSLASH", "TOP_INTER_OPEN",
+ "ANY_STR", "ESC_INTER", "TOP_ESC", "BACKSLASHES", "DOLLAR",
+ "INTER_OPEN", "BRACE_OPEN", "BRACE_CLOSE", "QUOTE_OPEN_SINGLE",
+ "QUOTE_OPEN_DOUBLE", "COMMA", "BRACKET_OPEN", "BRACKET_CLOSE",
+ "COLON", "POINT_FLOAT", "EXPONENT_FLOAT", "FLOAT", "INT",
+ "BOOL", "NULL", "UNQUOTED_CHAR", "ID", "ESC", "WS", "NESTED_INTER_OPEN",
+ "INTER_COLON", "INTER_CLOSE", "DOT", "INTER_BRACKET_OPEN",
+ "INTER_BRACKET_CLOSE", "INTER_ID", "INTER_KEY", "QSINGLE_INTER_OPEN",
+ "MATCHING_QUOTE_CLOSE", "QSINGLE_STR", "QSINGLE_ESC_INTER",
+ "QSINGLE_ESC_QUOTE", "QUOTED_ESC", "QSINGLE_BACKSLASHES",
+ "QSINGLE_DOLLAR", "QDOUBLE_INTER_OPEN", "QDOUBLE_CLOSE",
+ "QDOUBLE_STR", "QDOUBLE_ESC_INTER", "QDOUBLE_ESC_QUOTE",
+ "QDOUBLE_ESC", "QDOUBLE_BACKSLASHES", "QDOUBLE_DOLLAR" ]
+
+ grammarFileName = "OmegaConfGrammarLexer.g4"
+
+ def __init__(self, input=None, output:TextIO = sys.stdout):
+ super().__init__(input, output)
+ self.checkVersion("4.9.3")
+ self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache())
+ self._actions = None
+ self._predicates = None
+
+
diff --git a/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarParser.py b/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarParser.py
new file mode 100644
index 0000000000000000000000000000000000000000..024cbab87fddd6ed3b4abb63105589f673b0e864
--- /dev/null
+++ b/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarParser.py
@@ -0,0 +1,1595 @@
+# Generated from /tmp/build-via-sdist-fm63w174/omegaconf-2.3.0/omegaconf/grammar/OmegaConfGrammarParser.g4 by ANTLR 4.9.3
+# encoding: utf-8
+from antlr4 import *
+from io import StringIO
+import sys
+if sys.version_info[1] > 5:
+ from typing import TextIO
+else:
+ from typing.io import TextIO
+
+
+def serializedATN():
+ with StringIO() as buf:
+ buf.write("\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\3\36")
+ buf.write("\u00b7\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7")
+ buf.write("\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r\4\16")
+ buf.write("\t\16\4\17\t\17\4\20\t\20\4\21\t\21\3\2\3\2\3\2\3\3\3")
+ buf.write("\3\3\3\3\4\3\4\3\4\3\4\3\4\3\4\6\4/\n\4\r\4\16\4\60\3")
+ buf.write("\5\3\5\3\5\3\5\5\5\67\n\5\3\6\3\6\5\6;\n\6\3\6\3\6\3\7")
+ buf.write("\3\7\3\7\3\7\7\7C\n\7\f\7\16\7F\13\7\5\7H\n\7\3\7\3\7")
+ buf.write("\3\b\3\b\3\b\3\b\3\t\3\t\3\t\5\tS\n\t\7\tU\n\t\f\t\16")
+ buf.write("\tX\13\t\3\t\3\t\5\t\\\n\t\6\t^\n\t\r\t\16\t_\5\tb\n\t")
+ buf.write("\3\n\3\n\5\nf\n\n\3\13\3\13\7\13j\n\13\f\13\16\13m\13")
+ buf.write("\13\3\13\3\13\3\13\3\13\3\13\5\13t\n\13\3\13\3\13\3\13")
+ buf.write("\3\13\3\13\3\13\7\13|\n\13\f\13\16\13\177\13\13\3\13\3")
+ buf.write("\13\3\f\3\f\3\f\3\f\5\f\u0087\n\f\3\f\3\f\3\r\3\r\3\r")
+ buf.write("\5\r\u008e\n\r\3\16\3\16\5\16\u0092\n\16\3\16\3\16\3\16")
+ buf.write("\5\16\u0097\n\16\7\16\u0099\n\16\f\16\16\16\u009c\13\16")
+ buf.write("\3\17\3\17\5\17\u00a0\n\17\3\17\3\17\3\20\3\20\3\20\3")
+ buf.write("\20\3\20\3\20\3\20\3\20\3\20\3\20\6\20\u00ae\n\20\r\20")
+ buf.write("\16\20\u00af\3\21\6\21\u00b3\n\21\r\21\16\21\u00b4\3\21")
+ buf.write("\2\2\22\2\4\6\b\n\f\16\20\22\24\26\30\32\34\36 \2\4\3")
+ buf.write("\2\t\n\3\2\17\26\2\u00ce\2\"\3\2\2\2\4%\3\2\2\2\6.\3\2")
+ buf.write("\2\2\b\66\3\2\2\2\n8\3\2\2\2\f>\3\2\2\2\16K\3\2\2\2\20")
+ buf.write("a\3\2\2\2\22e\3\2\2\2\24g\3\2\2\2\26\u0082\3\2\2\2\30")
+ buf.write("\u008d\3\2\2\2\32\u0091\3\2\2\2\34\u009d\3\2\2\2\36\u00ad")
+ buf.write("\3\2\2\2 \u00b2\3\2\2\2\"#\5\6\4\2#$\7\2\2\3$\3\3\2\2")
+ buf.write("\2%&\5\b\5\2&\'\7\2\2\3\'\5\3\2\2\2(/\5\22\n\2)/\7\3\2")
+ buf.write("\2*/\7\25\2\2+/\7\4\2\2,/\7\5\2\2-/\7\33\2\2.(\3\2\2\2")
+ buf.write(".)\3\2\2\2.*\3\2\2\2.+\3\2\2\2.,\3\2\2\2.-\3\2\2\2/\60")
+ buf.write("\3\2\2\2\60.\3\2\2\2\60\61\3\2\2\2\61\7\3\2\2\2\62\67")
+ buf.write("\5\36\20\2\63\67\5\34\17\2\64\67\5\n\6\2\65\67\5\f\7\2")
+ buf.write("\66\62\3\2\2\2\66\63\3\2\2\2\66\64\3\2\2\2\66\65\3\2\2")
+ buf.write("\2\67\t\3\2\2\28:\7\f\2\29;\5\20\t\2:9\3\2\2\2:;\3\2\2")
+ buf.write("\2;<\3\2\2\2<=\7\r\2\2=\13\3\2\2\2>G\7\7\2\2?D\5\16\b")
+ buf.write("\2@A\7\13\2\2AC\5\16\b\2B@\3\2\2\2CF\3\2\2\2DB\3\2\2\2")
+ buf.write("DE\3\2\2\2EH\3\2\2\2FD\3\2\2\2G?\3\2\2\2GH\3\2\2\2HI\3")
+ buf.write("\2\2\2IJ\7\b\2\2J\r\3\2\2\2KL\5 \21\2LM\7\16\2\2MN\5\b")
+ buf.write("\5\2N\17\3\2\2\2OV\5\b\5\2PR\7\13\2\2QS\5\b\5\2RQ\3\2")
+ buf.write("\2\2RS\3\2\2\2SU\3\2\2\2TP\3\2\2\2UX\3\2\2\2VT\3\2\2\2")
+ buf.write("VW\3\2\2\2Wb\3\2\2\2XV\3\2\2\2Y[\7\13\2\2Z\\\5\b\5\2[")
+ buf.write("Z\3\2\2\2[\\\3\2\2\2\\^\3\2\2\2]Y\3\2\2\2^_\3\2\2\2_]")
+ buf.write("\3\2\2\2_`\3\2\2\2`b\3\2\2\2aO\3\2\2\2a]\3\2\2\2b\21\3")
+ buf.write("\2\2\2cf\5\24\13\2df\5\26\f\2ec\3\2\2\2ed\3\2\2\2f\23")
+ buf.write("\3\2\2\2gk\7\6\2\2hj\7\30\2\2ih\3\2\2\2jm\3\2\2\2ki\3")
+ buf.write("\2\2\2kl\3\2\2\2ls\3\2\2\2mk\3\2\2\2nt\5\30\r\2op\7\f")
+ buf.write("\2\2pq\5\30\r\2qr\7\r\2\2rt\3\2\2\2sn\3\2\2\2so\3\2\2")
+ buf.write("\2t}\3\2\2\2uv\7\30\2\2v|\5\30\r\2wx\7\f\2\2xy\5\30\r")
+ buf.write("\2yz\7\r\2\2z|\3\2\2\2{u\3\2\2\2{w\3\2\2\2|\177\3\2\2")
+ buf.write("\2}{\3\2\2\2}~\3\2\2\2~\u0080\3\2\2\2\177}\3\2\2\2\u0080")
+ buf.write("\u0081\7\27\2\2\u0081\25\3\2\2\2\u0082\u0083\7\6\2\2\u0083")
+ buf.write("\u0084\5\32\16\2\u0084\u0086\7\16\2\2\u0085\u0087\5\20")
+ buf.write("\t\2\u0086\u0085\3\2\2\2\u0086\u0087\3\2\2\2\u0087\u0088")
+ buf.write("\3\2\2\2\u0088\u0089\7\b\2\2\u0089\27\3\2\2\2\u008a\u008e")
+ buf.write("\5\22\n\2\u008b\u008e\7\24\2\2\u008c\u008e\7\31\2\2\u008d")
+ buf.write("\u008a\3\2\2\2\u008d\u008b\3\2\2\2\u008d\u008c\3\2\2\2")
+ buf.write("\u008e\31\3\2\2\2\u008f\u0092\5\22\n\2\u0090\u0092\7\24")
+ buf.write("\2\2\u0091\u008f\3\2\2\2\u0091\u0090\3\2\2\2\u0092\u009a")
+ buf.write("\3\2\2\2\u0093\u0096\7\30\2\2\u0094\u0097\5\22\n\2\u0095")
+ buf.write("\u0097\7\24\2\2\u0096\u0094\3\2\2\2\u0096\u0095\3\2\2")
+ buf.write("\2\u0097\u0099\3\2\2\2\u0098\u0093\3\2\2\2\u0099\u009c")
+ buf.write("\3\2\2\2\u009a\u0098\3\2\2\2\u009a\u009b\3\2\2\2\u009b")
+ buf.write("\33\3\2\2\2\u009c\u009a\3\2\2\2\u009d\u009f\t\2\2\2\u009e")
+ buf.write("\u00a0\5\6\4\2\u009f\u009e\3\2\2\2\u009f\u00a0\3\2\2\2")
+ buf.write("\u00a0\u00a1\3\2\2\2\u00a1\u00a2\7\32\2\2\u00a2\35\3\2")
+ buf.write("\2\2\u00a3\u00ae\7\24\2\2\u00a4\u00ae\7\22\2\2\u00a5\u00ae")
+ buf.write("\7\20\2\2\u00a6\u00ae\7\17\2\2\u00a7\u00ae\7\21\2\2\u00a8")
+ buf.write("\u00ae\7\23\2\2\u00a9\u00ae\7\16\2\2\u00aa\u00ae\7\25")
+ buf.write("\2\2\u00ab\u00ae\7\26\2\2\u00ac\u00ae\5\22\n\2\u00ad\u00a3")
+ buf.write("\3\2\2\2\u00ad\u00a4\3\2\2\2\u00ad\u00a5\3\2\2\2\u00ad")
+ buf.write("\u00a6\3\2\2\2\u00ad\u00a7\3\2\2\2\u00ad\u00a8\3\2\2\2")
+ buf.write("\u00ad\u00a9\3\2\2\2\u00ad\u00aa\3\2\2\2\u00ad\u00ab\3")
+ buf.write("\2\2\2\u00ad\u00ac\3\2\2\2\u00ae\u00af\3\2\2\2\u00af\u00ad")
+ buf.write("\3\2\2\2\u00af\u00b0\3\2\2\2\u00b0\37\3\2\2\2\u00b1\u00b3")
+ buf.write("\t\3\2\2\u00b2\u00b1\3\2\2\2\u00b3\u00b4\3\2\2\2\u00b4")
+ buf.write("\u00b2\3\2\2\2\u00b4\u00b5\3\2\2\2\u00b5!\3\2\2\2\33.")
+ buf.write("\60\66:DGRV[_aeks{}\u0086\u008d\u0091\u0096\u009a\u009f")
+ buf.write("\u00ad\u00af\u00b4")
+ return buf.getvalue()
+
+
+class OmegaConfGrammarParser ( Parser ):
+
+ grammarFileName = "OmegaConfGrammarParser.g4"
+
+ atn = ATNDeserializer().deserialize(serializedATN())
+
+ decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
+
+ sharedContextCache = PredictionContextCache()
+
+ literalNames = [ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "'.'", "", "",
+ "", "", "'['", "']'" ]
+
+ symbolicNames = [ "", "ANY_STR", "ESC_INTER", "TOP_ESC", "INTER_OPEN",
+ "BRACE_OPEN", "BRACE_CLOSE", "QUOTE_OPEN_SINGLE",
+ "QUOTE_OPEN_DOUBLE", "COMMA", "BRACKET_OPEN", "BRACKET_CLOSE",
+ "COLON", "FLOAT", "INT", "BOOL", "NULL", "UNQUOTED_CHAR",
+ "ID", "ESC", "WS", "INTER_CLOSE", "DOT", "INTER_KEY",
+ "MATCHING_QUOTE_CLOSE", "QUOTED_ESC", "DOLLAR", "INTER_BRACKET_OPEN",
+ "INTER_BRACKET_CLOSE" ]
+
+ RULE_configValue = 0
+ RULE_singleElement = 1
+ RULE_text = 2
+ RULE_element = 3
+ RULE_listContainer = 4
+ RULE_dictContainer = 5
+ RULE_dictKeyValuePair = 6
+ RULE_sequence = 7
+ RULE_interpolation = 8
+ RULE_interpolationNode = 9
+ RULE_interpolationResolver = 10
+ RULE_configKey = 11
+ RULE_resolverName = 12
+ RULE_quotedValue = 13
+ RULE_primitive = 14
+ RULE_dictKey = 15
+
+ ruleNames = [ "configValue", "singleElement", "text", "element", "listContainer",
+ "dictContainer", "dictKeyValuePair", "sequence", "interpolation",
+ "interpolationNode", "interpolationResolver", "configKey",
+ "resolverName", "quotedValue", "primitive", "dictKey" ]
+
+ EOF = Token.EOF
+ ANY_STR=1
+ ESC_INTER=2
+ TOP_ESC=3
+ INTER_OPEN=4
+ BRACE_OPEN=5
+ BRACE_CLOSE=6
+ QUOTE_OPEN_SINGLE=7
+ QUOTE_OPEN_DOUBLE=8
+ COMMA=9
+ BRACKET_OPEN=10
+ BRACKET_CLOSE=11
+ COLON=12
+ FLOAT=13
+ INT=14
+ BOOL=15
+ NULL=16
+ UNQUOTED_CHAR=17
+ ID=18
+ ESC=19
+ WS=20
+ INTER_CLOSE=21
+ DOT=22
+ INTER_KEY=23
+ MATCHING_QUOTE_CLOSE=24
+ QUOTED_ESC=25
+ DOLLAR=26
+ INTER_BRACKET_OPEN=27
+ INTER_BRACKET_CLOSE=28
+
+ def __init__(self, input:TokenStream, output:TextIO = sys.stdout):
+ super().__init__(input, output)
+ self.checkVersion("4.9.3")
+ self._interp = ParserATNSimulator(self, self.atn, self.decisionsToDFA, self.sharedContextCache)
+ self._predicates = None
+
+
+
+
+ class ConfigValueContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def text(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.TextContext,0)
+
+
+ def EOF(self):
+ return self.getToken(OmegaConfGrammarParser.EOF, 0)
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_configValue
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterConfigValue" ):
+ listener.enterConfigValue(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitConfigValue" ):
+ listener.exitConfigValue(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitConfigValue" ):
+ return visitor.visitConfigValue(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def configValue(self):
+
+ localctx = OmegaConfGrammarParser.ConfigValueContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 0, self.RULE_configValue)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 32
+ self.text()
+ self.state = 33
+ self.match(OmegaConfGrammarParser.EOF)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class SingleElementContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def element(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.ElementContext,0)
+
+
+ def EOF(self):
+ return self.getToken(OmegaConfGrammarParser.EOF, 0)
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_singleElement
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterSingleElement" ):
+ listener.enterSingleElement(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitSingleElement" ):
+ listener.exitSingleElement(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitSingleElement" ):
+ return visitor.visitSingleElement(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def singleElement(self):
+
+ localctx = OmegaConfGrammarParser.SingleElementContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 2, self.RULE_singleElement)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 35
+ self.element()
+ self.state = 36
+ self.match(OmegaConfGrammarParser.EOF)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class TextContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def interpolation(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(OmegaConfGrammarParser.InterpolationContext)
+ else:
+ return self.getTypedRuleContext(OmegaConfGrammarParser.InterpolationContext,i)
+
+
+ def ANY_STR(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.ANY_STR)
+ else:
+ return self.getToken(OmegaConfGrammarParser.ANY_STR, i)
+
+ def ESC(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.ESC)
+ else:
+ return self.getToken(OmegaConfGrammarParser.ESC, i)
+
+ def ESC_INTER(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.ESC_INTER)
+ else:
+ return self.getToken(OmegaConfGrammarParser.ESC_INTER, i)
+
+ def TOP_ESC(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.TOP_ESC)
+ else:
+ return self.getToken(OmegaConfGrammarParser.TOP_ESC, i)
+
+ def QUOTED_ESC(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.QUOTED_ESC)
+ else:
+ return self.getToken(OmegaConfGrammarParser.QUOTED_ESC, i)
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_text
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterText" ):
+ listener.enterText(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitText" ):
+ listener.exitText(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitText" ):
+ return visitor.visitText(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def text(self):
+
+ localctx = OmegaConfGrammarParser.TextContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 4, self.RULE_text)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 44
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while True:
+ self.state = 44
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [OmegaConfGrammarParser.INTER_OPEN]:
+ self.state = 38
+ self.interpolation()
+ pass
+ elif token in [OmegaConfGrammarParser.ANY_STR]:
+ self.state = 39
+ self.match(OmegaConfGrammarParser.ANY_STR)
+ pass
+ elif token in [OmegaConfGrammarParser.ESC]:
+ self.state = 40
+ self.match(OmegaConfGrammarParser.ESC)
+ pass
+ elif token in [OmegaConfGrammarParser.ESC_INTER]:
+ self.state = 41
+ self.match(OmegaConfGrammarParser.ESC_INTER)
+ pass
+ elif token in [OmegaConfGrammarParser.TOP_ESC]:
+ self.state = 42
+ self.match(OmegaConfGrammarParser.TOP_ESC)
+ pass
+ elif token in [OmegaConfGrammarParser.QUOTED_ESC]:
+ self.state = 43
+ self.match(OmegaConfGrammarParser.QUOTED_ESC)
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ self.state = 46
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if not ((((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << OmegaConfGrammarParser.ANY_STR) | (1 << OmegaConfGrammarParser.ESC_INTER) | (1 << OmegaConfGrammarParser.TOP_ESC) | (1 << OmegaConfGrammarParser.INTER_OPEN) | (1 << OmegaConfGrammarParser.ESC) | (1 << OmegaConfGrammarParser.QUOTED_ESC))) != 0)):
+ break
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class ElementContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def primitive(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.PrimitiveContext,0)
+
+
+ def quotedValue(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.QuotedValueContext,0)
+
+
+ def listContainer(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.ListContainerContext,0)
+
+
+ def dictContainer(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.DictContainerContext,0)
+
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_element
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterElement" ):
+ listener.enterElement(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitElement" ):
+ listener.exitElement(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitElement" ):
+ return visitor.visitElement(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def element(self):
+
+ localctx = OmegaConfGrammarParser.ElementContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 6, self.RULE_element)
+ try:
+ self.state = 52
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [OmegaConfGrammarParser.INTER_OPEN, OmegaConfGrammarParser.COLON, OmegaConfGrammarParser.FLOAT, OmegaConfGrammarParser.INT, OmegaConfGrammarParser.BOOL, OmegaConfGrammarParser.NULL, OmegaConfGrammarParser.UNQUOTED_CHAR, OmegaConfGrammarParser.ID, OmegaConfGrammarParser.ESC, OmegaConfGrammarParser.WS]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 48
+ self.primitive()
+ pass
+ elif token in [OmegaConfGrammarParser.QUOTE_OPEN_SINGLE, OmegaConfGrammarParser.QUOTE_OPEN_DOUBLE]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 49
+ self.quotedValue()
+ pass
+ elif token in [OmegaConfGrammarParser.BRACKET_OPEN]:
+ self.enterOuterAlt(localctx, 3)
+ self.state = 50
+ self.listContainer()
+ pass
+ elif token in [OmegaConfGrammarParser.BRACE_OPEN]:
+ self.enterOuterAlt(localctx, 4)
+ self.state = 51
+ self.dictContainer()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class ListContainerContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def BRACKET_OPEN(self):
+ return self.getToken(OmegaConfGrammarParser.BRACKET_OPEN, 0)
+
+ def BRACKET_CLOSE(self):
+ return self.getToken(OmegaConfGrammarParser.BRACKET_CLOSE, 0)
+
+ def sequence(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.SequenceContext,0)
+
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_listContainer
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterListContainer" ):
+ listener.enterListContainer(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitListContainer" ):
+ listener.exitListContainer(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitListContainer" ):
+ return visitor.visitListContainer(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def listContainer(self):
+
+ localctx = OmegaConfGrammarParser.ListContainerContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 8, self.RULE_listContainer)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 54
+ self.match(OmegaConfGrammarParser.BRACKET_OPEN)
+ self.state = 56
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << OmegaConfGrammarParser.INTER_OPEN) | (1 << OmegaConfGrammarParser.BRACE_OPEN) | (1 << OmegaConfGrammarParser.QUOTE_OPEN_SINGLE) | (1 << OmegaConfGrammarParser.QUOTE_OPEN_DOUBLE) | (1 << OmegaConfGrammarParser.COMMA) | (1 << OmegaConfGrammarParser.BRACKET_OPEN) | (1 << OmegaConfGrammarParser.COLON) | (1 << OmegaConfGrammarParser.FLOAT) | (1 << OmegaConfGrammarParser.INT) | (1 << OmegaConfGrammarParser.BOOL) | (1 << OmegaConfGrammarParser.NULL) | (1 << OmegaConfGrammarParser.UNQUOTED_CHAR) | (1 << OmegaConfGrammarParser.ID) | (1 << OmegaConfGrammarParser.ESC) | (1 << OmegaConfGrammarParser.WS))) != 0):
+ self.state = 55
+ self.sequence()
+
+
+ self.state = 58
+ self.match(OmegaConfGrammarParser.BRACKET_CLOSE)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class DictContainerContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def BRACE_OPEN(self):
+ return self.getToken(OmegaConfGrammarParser.BRACE_OPEN, 0)
+
+ def BRACE_CLOSE(self):
+ return self.getToken(OmegaConfGrammarParser.BRACE_CLOSE, 0)
+
+ def dictKeyValuePair(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(OmegaConfGrammarParser.DictKeyValuePairContext)
+ else:
+ return self.getTypedRuleContext(OmegaConfGrammarParser.DictKeyValuePairContext,i)
+
+
+ def COMMA(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.COMMA)
+ else:
+ return self.getToken(OmegaConfGrammarParser.COMMA, i)
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_dictContainer
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterDictContainer" ):
+ listener.enterDictContainer(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitDictContainer" ):
+ listener.exitDictContainer(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitDictContainer" ):
+ return visitor.visitDictContainer(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def dictContainer(self):
+
+ localctx = OmegaConfGrammarParser.DictContainerContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 10, self.RULE_dictContainer)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 60
+ self.match(OmegaConfGrammarParser.BRACE_OPEN)
+ self.state = 69
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << OmegaConfGrammarParser.FLOAT) | (1 << OmegaConfGrammarParser.INT) | (1 << OmegaConfGrammarParser.BOOL) | (1 << OmegaConfGrammarParser.NULL) | (1 << OmegaConfGrammarParser.UNQUOTED_CHAR) | (1 << OmegaConfGrammarParser.ID) | (1 << OmegaConfGrammarParser.ESC) | (1 << OmegaConfGrammarParser.WS))) != 0):
+ self.state = 61
+ self.dictKeyValuePair()
+ self.state = 66
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==OmegaConfGrammarParser.COMMA:
+ self.state = 62
+ self.match(OmegaConfGrammarParser.COMMA)
+ self.state = 63
+ self.dictKeyValuePair()
+ self.state = 68
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+
+
+ self.state = 71
+ self.match(OmegaConfGrammarParser.BRACE_CLOSE)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class DictKeyValuePairContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def dictKey(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.DictKeyContext,0)
+
+
+ def COLON(self):
+ return self.getToken(OmegaConfGrammarParser.COLON, 0)
+
+ def element(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.ElementContext,0)
+
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_dictKeyValuePair
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterDictKeyValuePair" ):
+ listener.enterDictKeyValuePair(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitDictKeyValuePair" ):
+ listener.exitDictKeyValuePair(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitDictKeyValuePair" ):
+ return visitor.visitDictKeyValuePair(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def dictKeyValuePair(self):
+
+ localctx = OmegaConfGrammarParser.DictKeyValuePairContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 12, self.RULE_dictKeyValuePair)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 73
+ self.dictKey()
+ self.state = 74
+ self.match(OmegaConfGrammarParser.COLON)
+ self.state = 75
+ self.element()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class SequenceContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def element(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(OmegaConfGrammarParser.ElementContext)
+ else:
+ return self.getTypedRuleContext(OmegaConfGrammarParser.ElementContext,i)
+
+
+ def COMMA(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.COMMA)
+ else:
+ return self.getToken(OmegaConfGrammarParser.COMMA, i)
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_sequence
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterSequence" ):
+ listener.enterSequence(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitSequence" ):
+ listener.exitSequence(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitSequence" ):
+ return visitor.visitSequence(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def sequence(self):
+
+ localctx = OmegaConfGrammarParser.SequenceContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 14, self.RULE_sequence)
+ self._la = 0 # Token type
+ try:
+ self.state = 95
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [OmegaConfGrammarParser.INTER_OPEN, OmegaConfGrammarParser.BRACE_OPEN, OmegaConfGrammarParser.QUOTE_OPEN_SINGLE, OmegaConfGrammarParser.QUOTE_OPEN_DOUBLE, OmegaConfGrammarParser.BRACKET_OPEN, OmegaConfGrammarParser.COLON, OmegaConfGrammarParser.FLOAT, OmegaConfGrammarParser.INT, OmegaConfGrammarParser.BOOL, OmegaConfGrammarParser.NULL, OmegaConfGrammarParser.UNQUOTED_CHAR, OmegaConfGrammarParser.ID, OmegaConfGrammarParser.ESC, OmegaConfGrammarParser.WS]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 77
+ self.element()
+ self.state = 84
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==OmegaConfGrammarParser.COMMA:
+ self.state = 78
+ self.match(OmegaConfGrammarParser.COMMA)
+ self.state = 80
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << OmegaConfGrammarParser.INTER_OPEN) | (1 << OmegaConfGrammarParser.BRACE_OPEN) | (1 << OmegaConfGrammarParser.QUOTE_OPEN_SINGLE) | (1 << OmegaConfGrammarParser.QUOTE_OPEN_DOUBLE) | (1 << OmegaConfGrammarParser.BRACKET_OPEN) | (1 << OmegaConfGrammarParser.COLON) | (1 << OmegaConfGrammarParser.FLOAT) | (1 << OmegaConfGrammarParser.INT) | (1 << OmegaConfGrammarParser.BOOL) | (1 << OmegaConfGrammarParser.NULL) | (1 << OmegaConfGrammarParser.UNQUOTED_CHAR) | (1 << OmegaConfGrammarParser.ID) | (1 << OmegaConfGrammarParser.ESC) | (1 << OmegaConfGrammarParser.WS))) != 0):
+ self.state = 79
+ self.element()
+
+
+ self.state = 86
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ pass
+ elif token in [OmegaConfGrammarParser.COMMA]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 91
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while True:
+ self.state = 87
+ self.match(OmegaConfGrammarParser.COMMA)
+ self.state = 89
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << OmegaConfGrammarParser.INTER_OPEN) | (1 << OmegaConfGrammarParser.BRACE_OPEN) | (1 << OmegaConfGrammarParser.QUOTE_OPEN_SINGLE) | (1 << OmegaConfGrammarParser.QUOTE_OPEN_DOUBLE) | (1 << OmegaConfGrammarParser.BRACKET_OPEN) | (1 << OmegaConfGrammarParser.COLON) | (1 << OmegaConfGrammarParser.FLOAT) | (1 << OmegaConfGrammarParser.INT) | (1 << OmegaConfGrammarParser.BOOL) | (1 << OmegaConfGrammarParser.NULL) | (1 << OmegaConfGrammarParser.UNQUOTED_CHAR) | (1 << OmegaConfGrammarParser.ID) | (1 << OmegaConfGrammarParser.ESC) | (1 << OmegaConfGrammarParser.WS))) != 0):
+ self.state = 88
+ self.element()
+
+
+ self.state = 93
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if not (_la==OmegaConfGrammarParser.COMMA):
+ break
+
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class InterpolationContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def interpolationNode(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.InterpolationNodeContext,0)
+
+
+ def interpolationResolver(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.InterpolationResolverContext,0)
+
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_interpolation
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterInterpolation" ):
+ listener.enterInterpolation(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitInterpolation" ):
+ listener.exitInterpolation(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitInterpolation" ):
+ return visitor.visitInterpolation(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def interpolation(self):
+
+ localctx = OmegaConfGrammarParser.InterpolationContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 16, self.RULE_interpolation)
+ try:
+ self.state = 99
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,11,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 97
+ self.interpolationNode()
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 98
+ self.interpolationResolver()
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class InterpolationNodeContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def INTER_OPEN(self):
+ return self.getToken(OmegaConfGrammarParser.INTER_OPEN, 0)
+
+ def INTER_CLOSE(self):
+ return self.getToken(OmegaConfGrammarParser.INTER_CLOSE, 0)
+
+ def configKey(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(OmegaConfGrammarParser.ConfigKeyContext)
+ else:
+ return self.getTypedRuleContext(OmegaConfGrammarParser.ConfigKeyContext,i)
+
+
+ def BRACKET_OPEN(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.BRACKET_OPEN)
+ else:
+ return self.getToken(OmegaConfGrammarParser.BRACKET_OPEN, i)
+
+ def BRACKET_CLOSE(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.BRACKET_CLOSE)
+ else:
+ return self.getToken(OmegaConfGrammarParser.BRACKET_CLOSE, i)
+
+ def DOT(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.DOT)
+ else:
+ return self.getToken(OmegaConfGrammarParser.DOT, i)
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_interpolationNode
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterInterpolationNode" ):
+ listener.enterInterpolationNode(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitInterpolationNode" ):
+ listener.exitInterpolationNode(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitInterpolationNode" ):
+ return visitor.visitInterpolationNode(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def interpolationNode(self):
+
+ localctx = OmegaConfGrammarParser.InterpolationNodeContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 18, self.RULE_interpolationNode)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 101
+ self.match(OmegaConfGrammarParser.INTER_OPEN)
+ self.state = 105
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==OmegaConfGrammarParser.DOT:
+ self.state = 102
+ self.match(OmegaConfGrammarParser.DOT)
+ self.state = 107
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ self.state = 113
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [OmegaConfGrammarParser.INTER_OPEN, OmegaConfGrammarParser.ID, OmegaConfGrammarParser.INTER_KEY]:
+ self.state = 108
+ self.configKey()
+ pass
+ elif token in [OmegaConfGrammarParser.BRACKET_OPEN]:
+ self.state = 109
+ self.match(OmegaConfGrammarParser.BRACKET_OPEN)
+ self.state = 110
+ self.configKey()
+ self.state = 111
+ self.match(OmegaConfGrammarParser.BRACKET_CLOSE)
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ self.state = 123
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==OmegaConfGrammarParser.BRACKET_OPEN or _la==OmegaConfGrammarParser.DOT:
+ self.state = 121
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [OmegaConfGrammarParser.DOT]:
+ self.state = 115
+ self.match(OmegaConfGrammarParser.DOT)
+ self.state = 116
+ self.configKey()
+ pass
+ elif token in [OmegaConfGrammarParser.BRACKET_OPEN]:
+ self.state = 117
+ self.match(OmegaConfGrammarParser.BRACKET_OPEN)
+ self.state = 118
+ self.configKey()
+ self.state = 119
+ self.match(OmegaConfGrammarParser.BRACKET_CLOSE)
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ self.state = 125
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ self.state = 126
+ self.match(OmegaConfGrammarParser.INTER_CLOSE)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class InterpolationResolverContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def INTER_OPEN(self):
+ return self.getToken(OmegaConfGrammarParser.INTER_OPEN, 0)
+
+ def resolverName(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.ResolverNameContext,0)
+
+
+ def COLON(self):
+ return self.getToken(OmegaConfGrammarParser.COLON, 0)
+
+ def BRACE_CLOSE(self):
+ return self.getToken(OmegaConfGrammarParser.BRACE_CLOSE, 0)
+
+ def sequence(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.SequenceContext,0)
+
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_interpolationResolver
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterInterpolationResolver" ):
+ listener.enterInterpolationResolver(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitInterpolationResolver" ):
+ listener.exitInterpolationResolver(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitInterpolationResolver" ):
+ return visitor.visitInterpolationResolver(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def interpolationResolver(self):
+
+ localctx = OmegaConfGrammarParser.InterpolationResolverContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 20, self.RULE_interpolationResolver)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 128
+ self.match(OmegaConfGrammarParser.INTER_OPEN)
+ self.state = 129
+ self.resolverName()
+ self.state = 130
+ self.match(OmegaConfGrammarParser.COLON)
+ self.state = 132
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << OmegaConfGrammarParser.INTER_OPEN) | (1 << OmegaConfGrammarParser.BRACE_OPEN) | (1 << OmegaConfGrammarParser.QUOTE_OPEN_SINGLE) | (1 << OmegaConfGrammarParser.QUOTE_OPEN_DOUBLE) | (1 << OmegaConfGrammarParser.COMMA) | (1 << OmegaConfGrammarParser.BRACKET_OPEN) | (1 << OmegaConfGrammarParser.COLON) | (1 << OmegaConfGrammarParser.FLOAT) | (1 << OmegaConfGrammarParser.INT) | (1 << OmegaConfGrammarParser.BOOL) | (1 << OmegaConfGrammarParser.NULL) | (1 << OmegaConfGrammarParser.UNQUOTED_CHAR) | (1 << OmegaConfGrammarParser.ID) | (1 << OmegaConfGrammarParser.ESC) | (1 << OmegaConfGrammarParser.WS))) != 0):
+ self.state = 131
+ self.sequence()
+
+
+ self.state = 134
+ self.match(OmegaConfGrammarParser.BRACE_CLOSE)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class ConfigKeyContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def interpolation(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.InterpolationContext,0)
+
+
+ def ID(self):
+ return self.getToken(OmegaConfGrammarParser.ID, 0)
+
+ def INTER_KEY(self):
+ return self.getToken(OmegaConfGrammarParser.INTER_KEY, 0)
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_configKey
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterConfigKey" ):
+ listener.enterConfigKey(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitConfigKey" ):
+ listener.exitConfigKey(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitConfigKey" ):
+ return visitor.visitConfigKey(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def configKey(self):
+
+ localctx = OmegaConfGrammarParser.ConfigKeyContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 22, self.RULE_configKey)
+ try:
+ self.state = 139
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [OmegaConfGrammarParser.INTER_OPEN]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 136
+ self.interpolation()
+ pass
+ elif token in [OmegaConfGrammarParser.ID]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 137
+ self.match(OmegaConfGrammarParser.ID)
+ pass
+ elif token in [OmegaConfGrammarParser.INTER_KEY]:
+ self.enterOuterAlt(localctx, 3)
+ self.state = 138
+ self.match(OmegaConfGrammarParser.INTER_KEY)
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class ResolverNameContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def interpolation(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(OmegaConfGrammarParser.InterpolationContext)
+ else:
+ return self.getTypedRuleContext(OmegaConfGrammarParser.InterpolationContext,i)
+
+
+ def ID(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.ID)
+ else:
+ return self.getToken(OmegaConfGrammarParser.ID, i)
+
+ def DOT(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.DOT)
+ else:
+ return self.getToken(OmegaConfGrammarParser.DOT, i)
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_resolverName
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterResolverName" ):
+ listener.enterResolverName(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitResolverName" ):
+ listener.exitResolverName(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitResolverName" ):
+ return visitor.visitResolverName(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def resolverName(self):
+
+ localctx = OmegaConfGrammarParser.ResolverNameContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 24, self.RULE_resolverName)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 143
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [OmegaConfGrammarParser.INTER_OPEN]:
+ self.state = 141
+ self.interpolation()
+ pass
+ elif token in [OmegaConfGrammarParser.ID]:
+ self.state = 142
+ self.match(OmegaConfGrammarParser.ID)
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ self.state = 152
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==OmegaConfGrammarParser.DOT:
+ self.state = 145
+ self.match(OmegaConfGrammarParser.DOT)
+ self.state = 148
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [OmegaConfGrammarParser.INTER_OPEN]:
+ self.state = 146
+ self.interpolation()
+ pass
+ elif token in [OmegaConfGrammarParser.ID]:
+ self.state = 147
+ self.match(OmegaConfGrammarParser.ID)
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ self.state = 154
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class QuotedValueContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def MATCHING_QUOTE_CLOSE(self):
+ return self.getToken(OmegaConfGrammarParser.MATCHING_QUOTE_CLOSE, 0)
+
+ def QUOTE_OPEN_SINGLE(self):
+ return self.getToken(OmegaConfGrammarParser.QUOTE_OPEN_SINGLE, 0)
+
+ def QUOTE_OPEN_DOUBLE(self):
+ return self.getToken(OmegaConfGrammarParser.QUOTE_OPEN_DOUBLE, 0)
+
+ def text(self):
+ return self.getTypedRuleContext(OmegaConfGrammarParser.TextContext,0)
+
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_quotedValue
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterQuotedValue" ):
+ listener.enterQuotedValue(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitQuotedValue" ):
+ listener.exitQuotedValue(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitQuotedValue" ):
+ return visitor.visitQuotedValue(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def quotedValue(self):
+
+ localctx = OmegaConfGrammarParser.QuotedValueContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 26, self.RULE_quotedValue)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 155
+ _la = self._input.LA(1)
+ if not(_la==OmegaConfGrammarParser.QUOTE_OPEN_SINGLE or _la==OmegaConfGrammarParser.QUOTE_OPEN_DOUBLE):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ self.state = 157
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << OmegaConfGrammarParser.ANY_STR) | (1 << OmegaConfGrammarParser.ESC_INTER) | (1 << OmegaConfGrammarParser.TOP_ESC) | (1 << OmegaConfGrammarParser.INTER_OPEN) | (1 << OmegaConfGrammarParser.ESC) | (1 << OmegaConfGrammarParser.QUOTED_ESC))) != 0):
+ self.state = 156
+ self.text()
+
+
+ self.state = 159
+ self.match(OmegaConfGrammarParser.MATCHING_QUOTE_CLOSE)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class PrimitiveContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def ID(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.ID)
+ else:
+ return self.getToken(OmegaConfGrammarParser.ID, i)
+
+ def NULL(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.NULL)
+ else:
+ return self.getToken(OmegaConfGrammarParser.NULL, i)
+
+ def INT(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.INT)
+ else:
+ return self.getToken(OmegaConfGrammarParser.INT, i)
+
+ def FLOAT(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.FLOAT)
+ else:
+ return self.getToken(OmegaConfGrammarParser.FLOAT, i)
+
+ def BOOL(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.BOOL)
+ else:
+ return self.getToken(OmegaConfGrammarParser.BOOL, i)
+
+ def UNQUOTED_CHAR(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.UNQUOTED_CHAR)
+ else:
+ return self.getToken(OmegaConfGrammarParser.UNQUOTED_CHAR, i)
+
+ def COLON(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.COLON)
+ else:
+ return self.getToken(OmegaConfGrammarParser.COLON, i)
+
+ def ESC(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.ESC)
+ else:
+ return self.getToken(OmegaConfGrammarParser.ESC, i)
+
+ def WS(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.WS)
+ else:
+ return self.getToken(OmegaConfGrammarParser.WS, i)
+
+ def interpolation(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(OmegaConfGrammarParser.InterpolationContext)
+ else:
+ return self.getTypedRuleContext(OmegaConfGrammarParser.InterpolationContext,i)
+
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_primitive
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterPrimitive" ):
+ listener.enterPrimitive(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitPrimitive" ):
+ listener.exitPrimitive(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitPrimitive" ):
+ return visitor.visitPrimitive(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def primitive(self):
+
+ localctx = OmegaConfGrammarParser.PrimitiveContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 28, self.RULE_primitive)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 171
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while True:
+ self.state = 171
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [OmegaConfGrammarParser.ID]:
+ self.state = 161
+ self.match(OmegaConfGrammarParser.ID)
+ pass
+ elif token in [OmegaConfGrammarParser.NULL]:
+ self.state = 162
+ self.match(OmegaConfGrammarParser.NULL)
+ pass
+ elif token in [OmegaConfGrammarParser.INT]:
+ self.state = 163
+ self.match(OmegaConfGrammarParser.INT)
+ pass
+ elif token in [OmegaConfGrammarParser.FLOAT]:
+ self.state = 164
+ self.match(OmegaConfGrammarParser.FLOAT)
+ pass
+ elif token in [OmegaConfGrammarParser.BOOL]:
+ self.state = 165
+ self.match(OmegaConfGrammarParser.BOOL)
+ pass
+ elif token in [OmegaConfGrammarParser.UNQUOTED_CHAR]:
+ self.state = 166
+ self.match(OmegaConfGrammarParser.UNQUOTED_CHAR)
+ pass
+ elif token in [OmegaConfGrammarParser.COLON]:
+ self.state = 167
+ self.match(OmegaConfGrammarParser.COLON)
+ pass
+ elif token in [OmegaConfGrammarParser.ESC]:
+ self.state = 168
+ self.match(OmegaConfGrammarParser.ESC)
+ pass
+ elif token in [OmegaConfGrammarParser.WS]:
+ self.state = 169
+ self.match(OmegaConfGrammarParser.WS)
+ pass
+ elif token in [OmegaConfGrammarParser.INTER_OPEN]:
+ self.state = 170
+ self.interpolation()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ self.state = 173
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if not ((((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << OmegaConfGrammarParser.INTER_OPEN) | (1 << OmegaConfGrammarParser.COLON) | (1 << OmegaConfGrammarParser.FLOAT) | (1 << OmegaConfGrammarParser.INT) | (1 << OmegaConfGrammarParser.BOOL) | (1 << OmegaConfGrammarParser.NULL) | (1 << OmegaConfGrammarParser.UNQUOTED_CHAR) | (1 << OmegaConfGrammarParser.ID) | (1 << OmegaConfGrammarParser.ESC) | (1 << OmegaConfGrammarParser.WS))) != 0)):
+ break
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class DictKeyContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def ID(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.ID)
+ else:
+ return self.getToken(OmegaConfGrammarParser.ID, i)
+
+ def NULL(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.NULL)
+ else:
+ return self.getToken(OmegaConfGrammarParser.NULL, i)
+
+ def INT(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.INT)
+ else:
+ return self.getToken(OmegaConfGrammarParser.INT, i)
+
+ def FLOAT(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.FLOAT)
+ else:
+ return self.getToken(OmegaConfGrammarParser.FLOAT, i)
+
+ def BOOL(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.BOOL)
+ else:
+ return self.getToken(OmegaConfGrammarParser.BOOL, i)
+
+ def UNQUOTED_CHAR(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.UNQUOTED_CHAR)
+ else:
+ return self.getToken(OmegaConfGrammarParser.UNQUOTED_CHAR, i)
+
+ def ESC(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.ESC)
+ else:
+ return self.getToken(OmegaConfGrammarParser.ESC, i)
+
+ def WS(self, i:int=None):
+ if i is None:
+ return self.getTokens(OmegaConfGrammarParser.WS)
+ else:
+ return self.getToken(OmegaConfGrammarParser.WS, i)
+
+ def getRuleIndex(self):
+ return OmegaConfGrammarParser.RULE_dictKey
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterDictKey" ):
+ listener.enterDictKey(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitDictKey" ):
+ listener.exitDictKey(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitDictKey" ):
+ return visitor.visitDictKey(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def dictKey(self):
+
+ localctx = OmegaConfGrammarParser.DictKeyContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 30, self.RULE_dictKey)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 176
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while True:
+ self.state = 175
+ _la = self._input.LA(1)
+ if not((((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << OmegaConfGrammarParser.FLOAT) | (1 << OmegaConfGrammarParser.INT) | (1 << OmegaConfGrammarParser.BOOL) | (1 << OmegaConfGrammarParser.NULL) | (1 << OmegaConfGrammarParser.UNQUOTED_CHAR) | (1 << OmegaConfGrammarParser.ID) | (1 << OmegaConfGrammarParser.ESC) | (1 << OmegaConfGrammarParser.WS))) != 0)):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ self.state = 178
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if not ((((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << OmegaConfGrammarParser.FLOAT) | (1 << OmegaConfGrammarParser.INT) | (1 << OmegaConfGrammarParser.BOOL) | (1 << OmegaConfGrammarParser.NULL) | (1 << OmegaConfGrammarParser.UNQUOTED_CHAR) | (1 << OmegaConfGrammarParser.ID) | (1 << OmegaConfGrammarParser.ESC) | (1 << OmegaConfGrammarParser.WS))) != 0)):
+ break
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+
+
+
diff --git a/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarParserListener.py b/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarParserListener.py
new file mode 100644
index 0000000000000000000000000000000000000000..d149348181e910757b6a3d6388073b951fac4678
--- /dev/null
+++ b/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarParserListener.py
@@ -0,0 +1,156 @@
+# Generated from /tmp/build-via-sdist-fm63w174/omegaconf-2.3.0/omegaconf/grammar/OmegaConfGrammarParser.g4 by ANTLR 4.9.3
+from antlr4 import *
+if __name__ is not None and "." in __name__:
+ from .OmegaConfGrammarParser import OmegaConfGrammarParser
+else:
+ from OmegaConfGrammarParser import OmegaConfGrammarParser
+
+# This class defines a complete listener for a parse tree produced by OmegaConfGrammarParser.
+class OmegaConfGrammarParserListener(ParseTreeListener):
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#configValue.
+ def enterConfigValue(self, ctx:OmegaConfGrammarParser.ConfigValueContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#configValue.
+ def exitConfigValue(self, ctx:OmegaConfGrammarParser.ConfigValueContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#singleElement.
+ def enterSingleElement(self, ctx:OmegaConfGrammarParser.SingleElementContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#singleElement.
+ def exitSingleElement(self, ctx:OmegaConfGrammarParser.SingleElementContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#text.
+ def enterText(self, ctx:OmegaConfGrammarParser.TextContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#text.
+ def exitText(self, ctx:OmegaConfGrammarParser.TextContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#element.
+ def enterElement(self, ctx:OmegaConfGrammarParser.ElementContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#element.
+ def exitElement(self, ctx:OmegaConfGrammarParser.ElementContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#listContainer.
+ def enterListContainer(self, ctx:OmegaConfGrammarParser.ListContainerContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#listContainer.
+ def exitListContainer(self, ctx:OmegaConfGrammarParser.ListContainerContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#dictContainer.
+ def enterDictContainer(self, ctx:OmegaConfGrammarParser.DictContainerContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#dictContainer.
+ def exitDictContainer(self, ctx:OmegaConfGrammarParser.DictContainerContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#dictKeyValuePair.
+ def enterDictKeyValuePair(self, ctx:OmegaConfGrammarParser.DictKeyValuePairContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#dictKeyValuePair.
+ def exitDictKeyValuePair(self, ctx:OmegaConfGrammarParser.DictKeyValuePairContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#sequence.
+ def enterSequence(self, ctx:OmegaConfGrammarParser.SequenceContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#sequence.
+ def exitSequence(self, ctx:OmegaConfGrammarParser.SequenceContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#interpolation.
+ def enterInterpolation(self, ctx:OmegaConfGrammarParser.InterpolationContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#interpolation.
+ def exitInterpolation(self, ctx:OmegaConfGrammarParser.InterpolationContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#interpolationNode.
+ def enterInterpolationNode(self, ctx:OmegaConfGrammarParser.InterpolationNodeContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#interpolationNode.
+ def exitInterpolationNode(self, ctx:OmegaConfGrammarParser.InterpolationNodeContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#interpolationResolver.
+ def enterInterpolationResolver(self, ctx:OmegaConfGrammarParser.InterpolationResolverContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#interpolationResolver.
+ def exitInterpolationResolver(self, ctx:OmegaConfGrammarParser.InterpolationResolverContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#configKey.
+ def enterConfigKey(self, ctx:OmegaConfGrammarParser.ConfigKeyContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#configKey.
+ def exitConfigKey(self, ctx:OmegaConfGrammarParser.ConfigKeyContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#resolverName.
+ def enterResolverName(self, ctx:OmegaConfGrammarParser.ResolverNameContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#resolverName.
+ def exitResolverName(self, ctx:OmegaConfGrammarParser.ResolverNameContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#quotedValue.
+ def enterQuotedValue(self, ctx:OmegaConfGrammarParser.QuotedValueContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#quotedValue.
+ def exitQuotedValue(self, ctx:OmegaConfGrammarParser.QuotedValueContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#primitive.
+ def enterPrimitive(self, ctx:OmegaConfGrammarParser.PrimitiveContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#primitive.
+ def exitPrimitive(self, ctx:OmegaConfGrammarParser.PrimitiveContext):
+ pass
+
+
+ # Enter a parse tree produced by OmegaConfGrammarParser#dictKey.
+ def enterDictKey(self, ctx:OmegaConfGrammarParser.DictKeyContext):
+ pass
+
+ # Exit a parse tree produced by OmegaConfGrammarParser#dictKey.
+ def exitDictKey(self, ctx:OmegaConfGrammarParser.DictKeyContext):
+ pass
+
+
+
+del OmegaConfGrammarParser
\ No newline at end of file
diff --git a/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarParserVisitor.py b/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarParserVisitor.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed247dd92f6654d2c862e848204a4679cc23e3e3
--- /dev/null
+++ b/cameras/deps/omegaconf/grammar/gen/OmegaConfGrammarParserVisitor.py
@@ -0,0 +1,93 @@
+# Generated from /tmp/build-via-sdist-fm63w174/omegaconf-2.3.0/omegaconf/grammar/OmegaConfGrammarParser.g4 by ANTLR 4.9.3
+from antlr4 import *
+if __name__ is not None and "." in __name__:
+ from .OmegaConfGrammarParser import OmegaConfGrammarParser
+else:
+ from OmegaConfGrammarParser import OmegaConfGrammarParser
+
+# This class defines a complete generic visitor for a parse tree produced by OmegaConfGrammarParser.
+
+class OmegaConfGrammarParserVisitor(ParseTreeVisitor):
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#configValue.
+ def visitConfigValue(self, ctx:OmegaConfGrammarParser.ConfigValueContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#singleElement.
+ def visitSingleElement(self, ctx:OmegaConfGrammarParser.SingleElementContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#text.
+ def visitText(self, ctx:OmegaConfGrammarParser.TextContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#element.
+ def visitElement(self, ctx:OmegaConfGrammarParser.ElementContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#listContainer.
+ def visitListContainer(self, ctx:OmegaConfGrammarParser.ListContainerContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#dictContainer.
+ def visitDictContainer(self, ctx:OmegaConfGrammarParser.DictContainerContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#dictKeyValuePair.
+ def visitDictKeyValuePair(self, ctx:OmegaConfGrammarParser.DictKeyValuePairContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#sequence.
+ def visitSequence(self, ctx:OmegaConfGrammarParser.SequenceContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#interpolation.
+ def visitInterpolation(self, ctx:OmegaConfGrammarParser.InterpolationContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#interpolationNode.
+ def visitInterpolationNode(self, ctx:OmegaConfGrammarParser.InterpolationNodeContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#interpolationResolver.
+ def visitInterpolationResolver(self, ctx:OmegaConfGrammarParser.InterpolationResolverContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#configKey.
+ def visitConfigKey(self, ctx:OmegaConfGrammarParser.ConfigKeyContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#resolverName.
+ def visitResolverName(self, ctx:OmegaConfGrammarParser.ResolverNameContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#quotedValue.
+ def visitQuotedValue(self, ctx:OmegaConfGrammarParser.QuotedValueContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#primitive.
+ def visitPrimitive(self, ctx:OmegaConfGrammarParser.PrimitiveContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by OmegaConfGrammarParser#dictKey.
+ def visitDictKey(self, ctx:OmegaConfGrammarParser.DictKeyContext):
+ return self.visitChildren(ctx)
+
+
+
+del OmegaConfGrammarParser
\ No newline at end of file
diff --git a/cameras/deps/omegaconf/grammar/gen/__init__.py b/cameras/deps/omegaconf/grammar/gen/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/cameras/deps/omegaconf/grammar_parser.py b/cameras/deps/omegaconf/grammar_parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c883c2cfd49b442a90bda0c7777cccca706e82d
--- /dev/null
+++ b/cameras/deps/omegaconf/grammar_parser.py
@@ -0,0 +1,144 @@
+import re
+import threading
+from typing import Any
+
+from antlr4 import CommonTokenStream, InputStream, ParserRuleContext
+from antlr4.error.ErrorListener import ErrorListener
+
+from .errors import GrammarParseError
+
+# Import from visitor in order to check the presence of generated grammar files
+# files in a single place.
+from .grammar_visitor import ( # type: ignore
+ OmegaConfGrammarLexer,
+ OmegaConfGrammarParser,
+)
+
+# Used to cache grammar objects to avoid re-creating them on each call to `parse()`.
+# We use a per-thread cache to make it thread-safe.
+_grammar_cache = threading.local()
+
+# Build regex pattern to efficiently identify typical interpolations.
+# See test `test_match_simple_interpolation_pattern` for examples.
+_config_key = r"[$\w]+" # foo, $0, $bar, $foo_$bar123$
+_key_maybe_brackets = f"{_config_key}|\\[{_config_key}\\]" # foo, [foo], [$bar]
+_node_access = f"\\.{_key_maybe_brackets}" # .foo, [foo], [$bar]
+_node_path = f"(\\.)*({_key_maybe_brackets})({_node_access})*" # [foo].bar, .foo[bar]
+_node_inter = f"\\${{\\s*{_node_path}\\s*}}" # node interpolation ${foo.bar}
+_id = "[a-zA-Z_][\\w\\-]*" # foo, foo_bar, foo-bar, abc123
+_resolver_name = f"({_id}(\\.{_id})*)?" # foo, ns.bar3, ns_1.ns_2.b0z
+_arg = r"[a-zA-Z_0-9/\-\+.$%*@?|]+" # string representing a resolver argument
+_args = f"{_arg}(\\s*,\\s*{_arg})*" # list of resolver arguments
+_resolver_inter = f"\\${{\\s*{_resolver_name}\\s*:\\s*{_args}?\\s*}}" # ${foo:bar}
+_inter = f"({_node_inter}|{_resolver_inter})" # any kind of interpolation
+_outer = "([^$]|\\$(?!{))+" # any character except $ (unless not followed by {)
+SIMPLE_INTERPOLATION_PATTERN = re.compile(
+ f"({_outer})?({_inter}({_outer})?)+$", flags=re.ASCII
+)
+# NOTE: SIMPLE_INTERPOLATION_PATTERN must not generate false positive matches:
+# it must not accept anything that isn't a valid interpolation (per the
+# interpolation grammar defined in `omegaconf/grammar/*.g4`).
+
+
+class OmegaConfErrorListener(ErrorListener): # type: ignore
+ def syntaxError(
+ self,
+ recognizer: Any,
+ offending_symbol: Any,
+ line: Any,
+ column: Any,
+ msg: Any,
+ e: Any,
+ ) -> None:
+ raise GrammarParseError(str(e) if msg is None else msg) from e
+
+ def reportAmbiguity(
+ self,
+ recognizer: Any,
+ dfa: Any,
+ startIndex: Any,
+ stopIndex: Any,
+ exact: Any,
+ ambigAlts: Any,
+ configs: Any,
+ ) -> None:
+ raise GrammarParseError("ANTLR error: Ambiguity") # pragma: no cover
+
+ def reportAttemptingFullContext(
+ self,
+ recognizer: Any,
+ dfa: Any,
+ startIndex: Any,
+ stopIndex: Any,
+ conflictingAlts: Any,
+ configs: Any,
+ ) -> None:
+ # Note: for now we raise an error to be safe. However this is mostly a
+ # performance warning, so in the future this may be relaxed if we need
+ # to change the grammar in such a way that this warning cannot be
+ # avoided (another option would be to switch to SLL parsing mode).
+ raise GrammarParseError(
+ "ANTLR error: Attempting Full Context"
+ ) # pragma: no cover
+
+ def reportContextSensitivity(
+ self,
+ recognizer: Any,
+ dfa: Any,
+ startIndex: Any,
+ stopIndex: Any,
+ prediction: Any,
+ configs: Any,
+ ) -> None:
+ raise GrammarParseError("ANTLR error: ContextSensitivity") # pragma: no cover
+
+
+def parse(
+ value: str, parser_rule: str = "configValue", lexer_mode: str = "DEFAULT_MODE"
+) -> ParserRuleContext:
+ """
+ Parse interpolated string `value` (and return the parse tree).
+ """
+ l_mode = getattr(OmegaConfGrammarLexer, lexer_mode)
+ istream = InputStream(value)
+
+ cached = getattr(_grammar_cache, "data", None)
+ if cached is None:
+ error_listener = OmegaConfErrorListener()
+ lexer = OmegaConfGrammarLexer(istream)
+ lexer.removeErrorListeners()
+ lexer.addErrorListener(error_listener)
+ lexer.mode(l_mode)
+ token_stream = CommonTokenStream(lexer)
+ parser = OmegaConfGrammarParser(token_stream)
+ parser.removeErrorListeners()
+ parser.addErrorListener(error_listener)
+
+ # The two lines below could be enabled in the future if we decide to switch
+ # to SLL prediction mode. Warning though, it has not been fully tested yet!
+ # from antlr4 import PredictionMode
+ # parser._interp.predictionMode = PredictionMode.SLL
+
+ # Note that although the input stream `istream` is implicitly cached within
+ # the lexer, it will be replaced by a new input next time the lexer is re-used.
+ _grammar_cache.data = lexer, token_stream, parser
+
+ else:
+ lexer, token_stream, parser = cached
+ # Replace the old input stream with the new one.
+ lexer.inputStream = istream
+ # Initialize the lexer / token stream / parser to process the new input.
+ lexer.mode(l_mode)
+ token_stream.setTokenSource(lexer)
+ parser.reset()
+
+ try:
+ return getattr(parser, parser_rule)()
+ except Exception as exc:
+ if type(exc) is Exception and str(exc) == "Empty Stack":
+ # This exception is raised by antlr when trying to pop a mode while
+ # no mode has been pushed. We convert it into an `GrammarParseError`
+ # to facilitate exception handling from the caller.
+ raise GrammarParseError("Empty Stack")
+ else:
+ raise
diff --git a/cameras/deps/omegaconf/grammar_visitor.py b/cameras/deps/omegaconf/grammar_visitor.py
new file mode 100644
index 0000000000000000000000000000000000000000..1771516dcf78bbc692512d58d4dea55850b957b7
--- /dev/null
+++ b/cameras/deps/omegaconf/grammar_visitor.py
@@ -0,0 +1,392 @@
+import sys
+import warnings
+from itertools import zip_longest
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Dict,
+ Generator,
+ List,
+ Optional,
+ Set,
+ Tuple,
+ Union,
+)
+
+from antlr4 import TerminalNode
+
+from .errors import InterpolationResolutionError
+
+if TYPE_CHECKING:
+ from .base import Node # noqa F401
+
+try:
+ from omegaconf.grammar.gen.OmegaConfGrammarLexer import OmegaConfGrammarLexer
+ from omegaconf.grammar.gen.OmegaConfGrammarParser import OmegaConfGrammarParser
+ from omegaconf.grammar.gen.OmegaConfGrammarParserVisitor import (
+ OmegaConfGrammarParserVisitor,
+ )
+
+except ModuleNotFoundError: # pragma: no cover
+ print(
+ "Error importing OmegaConf's generated parsers, run `python setup.py antlr` to regenerate.",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+
+class GrammarVisitor(OmegaConfGrammarParserVisitor):
+ def __init__(
+ self,
+ node_interpolation_callback: Callable[
+ [str, Optional[Set[int]]],
+ Optional["Node"],
+ ],
+ resolver_interpolation_callback: Callable[..., Any],
+ memo: Optional[Set[int]],
+ **kw: Dict[Any, Any],
+ ):
+ """
+ Constructor.
+
+ :param node_interpolation_callback: Callback function that is called when
+ needing to resolve a node interpolation. This function should take a single
+ string input which is the key's dot path (ex: `"foo.bar"`).
+
+ :param resolver_interpolation_callback: Callback function that is called when
+ needing to resolve a resolver interpolation. This function should accept
+ three keyword arguments: `name` (str, the name of the resolver),
+ `args` (tuple, the inputs to the resolver), and `args_str` (tuple,
+ the string representation of the inputs to the resolver).
+
+ :param kw: Additional keyword arguments to be forwarded to parent class.
+ """
+ super().__init__(**kw)
+ self.node_interpolation_callback = node_interpolation_callback
+ self.resolver_interpolation_callback = resolver_interpolation_callback
+ self.memo = memo
+
+ def aggregateResult(self, aggregate: List[Any], nextResult: Any) -> List[Any]:
+ raise NotImplementedError
+
+ def defaultResult(self) -> List[Any]:
+ # Raising an exception because not currently used (like `aggregateResult()`).
+ raise NotImplementedError
+
+ def visitConfigKey(self, ctx: OmegaConfGrammarParser.ConfigKeyContext) -> str:
+ from ._utils import _get_value
+
+ # interpolation | ID | INTER_KEY
+ assert ctx.getChildCount() == 1
+ child = ctx.getChild(0)
+ if isinstance(child, OmegaConfGrammarParser.InterpolationContext):
+ res = _get_value(self.visitInterpolation(child))
+ if not isinstance(res, str):
+ raise InterpolationResolutionError(
+ f"The following interpolation is used to denote a config key and "
+ f"thus should return a string, but instead returned `{res}` of "
+ f"type `{type(res)}`: {ctx.getChild(0).getText()}"
+ )
+ return res
+ else:
+ assert isinstance(child, TerminalNode) and isinstance(
+ child.symbol.text, str
+ )
+ return child.symbol.text
+
+ def visitConfigValue(self, ctx: OmegaConfGrammarParser.ConfigValueContext) -> Any:
+ # text EOF
+ assert ctx.getChildCount() == 2
+ return self.visit(ctx.getChild(0))
+
+ def visitDictKey(self, ctx: OmegaConfGrammarParser.DictKeyContext) -> Any:
+ return self._createPrimitive(ctx)
+
+ def visitDictContainer(
+ self, ctx: OmegaConfGrammarParser.DictContainerContext
+ ) -> Dict[Any, Any]:
+ # BRACE_OPEN (dictKeyValuePair (COMMA dictKeyValuePair)*)? BRACE_CLOSE
+ assert ctx.getChildCount() >= 2
+ return dict(
+ self.visitDictKeyValuePair(ctx.getChild(i))
+ for i in range(1, ctx.getChildCount() - 1, 2)
+ )
+
+ def visitElement(self, ctx: OmegaConfGrammarParser.ElementContext) -> Any:
+ # primitive | quotedValue | listContainer | dictContainer
+ assert ctx.getChildCount() == 1
+ return self.visit(ctx.getChild(0))
+
+ def visitInterpolation(
+ self, ctx: OmegaConfGrammarParser.InterpolationContext
+ ) -> Any:
+ assert ctx.getChildCount() == 1 # interpolationNode | interpolationResolver
+ return self.visit(ctx.getChild(0))
+
+ def visitInterpolationNode(
+ self, ctx: OmegaConfGrammarParser.InterpolationNodeContext
+ ) -> Optional["Node"]:
+ # INTER_OPEN
+ # DOT* // relative interpolation?
+ # (configKey | BRACKET_OPEN configKey BRACKET_CLOSE) // foo, [foo]
+ # (DOT configKey | BRACKET_OPEN configKey BRACKET_CLOSE)* // .foo, [foo], .foo[bar], [foo].bar[baz]
+ # INTER_CLOSE;
+
+ assert ctx.getChildCount() >= 3
+
+ inter_key_tokens = [] # parsed elements of the dot path
+ for child in ctx.getChildren():
+ if isinstance(child, TerminalNode):
+ s = child.symbol
+ if s.type in [
+ OmegaConfGrammarLexer.DOT,
+ OmegaConfGrammarLexer.BRACKET_OPEN,
+ OmegaConfGrammarLexer.BRACKET_CLOSE,
+ ]:
+ inter_key_tokens.append(s.text)
+ else:
+ assert s.type in (
+ OmegaConfGrammarLexer.INTER_OPEN,
+ OmegaConfGrammarLexer.INTER_CLOSE,
+ )
+ else:
+ assert isinstance(child, OmegaConfGrammarParser.ConfigKeyContext)
+ inter_key_tokens.append(self.visitConfigKey(child))
+
+ inter_key = "".join(inter_key_tokens)
+ return self.node_interpolation_callback(inter_key, self.memo)
+
+ def visitInterpolationResolver(
+ self, ctx: OmegaConfGrammarParser.InterpolationResolverContext
+ ) -> Any:
+
+ # INTER_OPEN resolverName COLON sequence? BRACE_CLOSE
+ assert 4 <= ctx.getChildCount() <= 5
+
+ resolver_name = self.visit(ctx.getChild(1))
+ maybe_seq = ctx.getChild(3)
+ args = []
+ args_str = []
+ if isinstance(maybe_seq, TerminalNode): # means there are no args
+ assert maybe_seq.symbol.type == OmegaConfGrammarLexer.BRACE_CLOSE
+ else:
+ assert isinstance(maybe_seq, OmegaConfGrammarParser.SequenceContext)
+ for val, txt in self.visitSequence(maybe_seq):
+ args.append(val)
+ args_str.append(txt)
+
+ return self.resolver_interpolation_callback(
+ name=resolver_name,
+ args=tuple(args),
+ args_str=tuple(args_str),
+ )
+
+ def visitDictKeyValuePair(
+ self, ctx: OmegaConfGrammarParser.DictKeyValuePairContext
+ ) -> Tuple[Any, Any]:
+ from ._utils import _get_value
+
+ assert ctx.getChildCount() == 3 # dictKey COLON element
+ key = self.visit(ctx.getChild(0))
+ colon = ctx.getChild(1)
+ assert (
+ isinstance(colon, TerminalNode)
+ and colon.symbol.type == OmegaConfGrammarLexer.COLON
+ )
+ value = _get_value(self.visitElement(ctx.getChild(2)))
+ return key, value
+
+ def visitListContainer(
+ self, ctx: OmegaConfGrammarParser.ListContainerContext
+ ) -> List[Any]:
+ # BRACKET_OPEN sequence? BRACKET_CLOSE;
+ assert ctx.getChildCount() in (2, 3)
+ if ctx.getChildCount() == 2:
+ return []
+ sequence = ctx.getChild(1)
+ assert isinstance(sequence, OmegaConfGrammarParser.SequenceContext)
+ return list(val for val, _ in self.visitSequence(sequence)) # ignore raw text
+
+ def visitPrimitive(self, ctx: OmegaConfGrammarParser.PrimitiveContext) -> Any:
+ return self._createPrimitive(ctx)
+
+ def visitQuotedValue(self, ctx: OmegaConfGrammarParser.QuotedValueContext) -> str:
+ # (QUOTE_OPEN_SINGLE | QUOTE_OPEN_DOUBLE) text? MATCHING_QUOTE_CLOSE
+ n = ctx.getChildCount()
+ assert n in [2, 3]
+ return str(self.visit(ctx.getChild(1))) if n == 3 else ""
+
+ def visitResolverName(self, ctx: OmegaConfGrammarParser.ResolverNameContext) -> str:
+ from ._utils import _get_value
+
+ # (interpolation | ID) (DOT (interpolation | ID))*
+ assert ctx.getChildCount() >= 1
+ items = []
+ for child in list(ctx.getChildren())[::2]:
+ if isinstance(child, TerminalNode):
+ assert child.symbol.type == OmegaConfGrammarLexer.ID
+ items.append(child.symbol.text)
+ else:
+ assert isinstance(child, OmegaConfGrammarParser.InterpolationContext)
+ item = _get_value(self.visitInterpolation(child))
+ if not isinstance(item, str):
+ raise InterpolationResolutionError(
+ f"The name of a resolver must be a string, but the interpolation "
+ f"{child.getText()} resolved to `{item}` which is of type "
+ f"{type(item)}"
+ )
+ items.append(item)
+ return ".".join(items)
+
+ def visitSequence(
+ self, ctx: OmegaConfGrammarParser.SequenceContext
+ ) -> Generator[Any, None, None]:
+ from ._utils import _get_value
+
+ # (element (COMMA element?)*) | (COMMA element?)+
+ assert ctx.getChildCount() >= 1
+
+ # DEPRECATED: remove in 2.2 (revert #571)
+ def empty_str_warning() -> None:
+ txt = ctx.getText()
+ warnings.warn(
+ f"In the sequence `{txt}` some elements are missing: please replace "
+ f"them with empty quoted strings. "
+ f"See https://github.com/omry/omegaconf/issues/572 for details.",
+ category=UserWarning,
+ )
+
+ is_previous_comma = True # whether previous child was a comma (init to True)
+ for child in ctx.getChildren():
+ if isinstance(child, OmegaConfGrammarParser.ElementContext):
+ # Also preserve the original text representation of `child` so
+ # as to allow backward compatibility with old resolvers (registered
+ # with `legacy_register_resolver()`). Note that we cannot just cast
+ # the value to string later as for instance `null` would become "None".
+ yield _get_value(self.visitElement(child)), child.getText()
+ is_previous_comma = False
+ else:
+ assert (
+ isinstance(child, TerminalNode)
+ and child.symbol.type == OmegaConfGrammarLexer.COMMA
+ )
+ if is_previous_comma:
+ empty_str_warning()
+ yield "", ""
+ else:
+ is_previous_comma = True
+ if is_previous_comma:
+ # Trailing comma.
+ empty_str_warning()
+ yield "", ""
+
+ def visitSingleElement(
+ self, ctx: OmegaConfGrammarParser.SingleElementContext
+ ) -> Any:
+ # element EOF
+ assert ctx.getChildCount() == 2
+ return self.visit(ctx.getChild(0))
+
+ def visitText(self, ctx: OmegaConfGrammarParser.TextContext) -> Any:
+ # (interpolation | ANY_STR | ESC | ESC_INTER | TOP_ESC | QUOTED_ESC)+
+
+ # Single interpolation? If yes, return its resolved value "as is".
+ if ctx.getChildCount() == 1:
+ c = ctx.getChild(0)
+ if isinstance(c, OmegaConfGrammarParser.InterpolationContext):
+ return self.visitInterpolation(c)
+
+ # Otherwise, concatenate string representations together.
+ return self._unescape(list(ctx.getChildren()))
+
+ def _createPrimitive(
+ self,
+ ctx: Union[
+ OmegaConfGrammarParser.PrimitiveContext,
+ OmegaConfGrammarParser.DictKeyContext,
+ ],
+ ) -> Any:
+ # (ID | NULL | INT | FLOAT | BOOL | UNQUOTED_CHAR | COLON | ESC | WS | interpolation)+
+ if ctx.getChildCount() == 1:
+ child = ctx.getChild(0)
+ if isinstance(child, OmegaConfGrammarParser.InterpolationContext):
+ return self.visitInterpolation(child)
+ assert isinstance(child, TerminalNode)
+ symbol = child.symbol
+ # Parse primitive types.
+ if symbol.type in (
+ OmegaConfGrammarLexer.ID,
+ OmegaConfGrammarLexer.UNQUOTED_CHAR,
+ OmegaConfGrammarLexer.COLON,
+ ):
+ return symbol.text
+ elif symbol.type == OmegaConfGrammarLexer.NULL:
+ return None
+ elif symbol.type == OmegaConfGrammarLexer.INT:
+ return int(symbol.text)
+ elif symbol.type == OmegaConfGrammarLexer.FLOAT:
+ return float(symbol.text)
+ elif symbol.type == OmegaConfGrammarLexer.BOOL:
+ return symbol.text.lower() == "true"
+ elif symbol.type == OmegaConfGrammarLexer.ESC:
+ return self._unescape([child])
+ elif symbol.type == OmegaConfGrammarLexer.WS: # pragma: no cover
+ # A single WS should have been "consumed" by another token.
+ raise AssertionError("WS should never be reached")
+ assert False, symbol.type
+ # Concatenation of multiple items ==> un-escape the concatenation.
+ return self._unescape(list(ctx.getChildren()))
+
+ def _unescape(
+ self,
+ seq: List[Union[TerminalNode, OmegaConfGrammarParser.InterpolationContext]],
+ ) -> str:
+ """
+ Concatenate all symbols / interpolations in `seq`, unescaping symbols as needed.
+
+ Interpolations are resolved and cast to string *WITHOUT* escaping their result
+ (it is assumed that whatever escaping is required was already handled during the
+ resolving of the interpolation).
+ """
+ chrs = []
+ for node, next_node in zip_longest(seq, seq[1:]):
+ if isinstance(node, TerminalNode):
+ s = node.symbol
+ if s.type == OmegaConfGrammarLexer.ESC_INTER:
+ # `ESC_INTER` is of the form `\\...\${`: the formula below computes
+ # the number of characters to keep at the end of the string to remove
+ # the correct number of backslashes.
+ text = s.text[-(len(s.text) // 2 + 1) :]
+ elif (
+ # Character sequence identified as requiring un-escaping.
+ s.type == OmegaConfGrammarLexer.ESC
+ or (
+ # At top level, we need to un-escape backslashes that precede
+ # an interpolation.
+ s.type == OmegaConfGrammarLexer.TOP_ESC
+ and isinstance(
+ next_node, OmegaConfGrammarParser.InterpolationContext
+ )
+ )
+ or (
+ # In a quoted sring, we need to un-escape backslashes that
+ # either end the string, or are followed by an interpolation.
+ s.type == OmegaConfGrammarLexer.QUOTED_ESC
+ and (
+ next_node is None
+ or isinstance(
+ next_node, OmegaConfGrammarParser.InterpolationContext
+ )
+ )
+ )
+ ):
+ text = s.text[1::2] # un-escape the sequence
+ else:
+ text = s.text # keep the original text
+ else:
+ assert isinstance(node, OmegaConfGrammarParser.InterpolationContext)
+ text = str(self.visitInterpolation(node))
+ chrs.append(text)
+
+ return "".join(chrs)
diff --git a/cameras/deps/omegaconf/listconfig.py b/cameras/deps/omegaconf/listconfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3b0f18a4e1cc69f033fec788fe674a2c81dc700
--- /dev/null
+++ b/cameras/deps/omegaconf/listconfig.py
@@ -0,0 +1,679 @@
+import copy
+import itertools
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ MutableSequence,
+ Optional,
+ Tuple,
+ Type,
+ Union,
+)
+
+from ._utils import (
+ ValueKind,
+ _is_missing_literal,
+ _is_none,
+ _resolve_optional,
+ format_and_raise,
+ get_value_kind,
+ is_int,
+ is_primitive_list,
+ is_structured_config,
+ type_str,
+)
+from .base import Box, ContainerMetadata, Node
+from .basecontainer import BaseContainer
+from .errors import (
+ ConfigAttributeError,
+ ConfigTypeError,
+ ConfigValueError,
+ KeyValidationError,
+ MissingMandatoryValue,
+ ReadonlyConfigError,
+ ValidationError,
+)
+
+
+class ListConfig(BaseContainer, MutableSequence[Any]):
+
+ _content: Union[List[Node], None, str]
+
+ def __init__(
+ self,
+ content: Union[List[Any], Tuple[Any, ...], "ListConfig", str, None],
+ key: Any = None,
+ parent: Optional[Box] = None,
+ element_type: Union[Type[Any], Any] = Any,
+ is_optional: bool = True,
+ ref_type: Union[Type[Any], Any] = Any,
+ flags: Optional[Dict[str, bool]] = None,
+ ) -> None:
+ try:
+ if isinstance(content, ListConfig):
+ if flags is None:
+ flags = content._metadata.flags
+ super().__init__(
+ parent=parent,
+ metadata=ContainerMetadata(
+ ref_type=ref_type,
+ object_type=list,
+ key=key,
+ optional=is_optional,
+ element_type=element_type,
+ key_type=int,
+ flags=flags,
+ ),
+ )
+
+ if isinstance(content, ListConfig):
+ metadata = copy.deepcopy(content._metadata)
+ metadata.key = key
+ metadata.ref_type = ref_type
+ metadata.optional = is_optional
+ metadata.element_type = element_type
+ self.__dict__["_metadata"] = metadata
+ self._set_value(value=content, flags=flags)
+ except Exception as ex:
+ format_and_raise(node=None, key=key, value=None, cause=ex, msg=str(ex))
+
+ def _validate_get(self, key: Any, value: Any = None) -> None:
+ if not isinstance(key, (int, slice)):
+ raise KeyValidationError(
+ "ListConfig indices must be integers or slices, not $KEY_TYPE"
+ )
+
+ def _validate_set(self, key: Any, value: Any) -> None:
+ from omegaconf import OmegaConf
+
+ self._validate_get(key, value)
+
+ if self._get_flag("readonly"):
+ raise ReadonlyConfigError("ListConfig is read-only")
+
+ if 0 <= key < self.__len__():
+ target = self._get_node(key)
+ if target is not None:
+ assert isinstance(target, Node)
+ if value is None and not target._is_optional():
+ raise ValidationError(
+ "$FULL_KEY is not optional and cannot be assigned None"
+ )
+
+ vk = get_value_kind(value)
+ if vk == ValueKind.MANDATORY_MISSING:
+ return
+ else:
+ is_optional, target_type = _resolve_optional(self._metadata.element_type)
+ value_type = OmegaConf.get_type(value)
+
+ if (value_type is None and not is_optional) or (
+ is_structured_config(target_type)
+ and value_type is not None
+ and not issubclass(value_type, target_type)
+ ):
+ msg = (
+ f"Invalid type assigned: {type_str(value_type)} is not a "
+ f"subclass of {type_str(target_type)}. value: {value}"
+ )
+ raise ValidationError(msg)
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "ListConfig":
+ res = ListConfig(None)
+ res.__dict__["_metadata"] = copy.deepcopy(self.__dict__["_metadata"], memo=memo)
+ res.__dict__["_flags_cache"] = copy.deepcopy(
+ self.__dict__["_flags_cache"], memo=memo
+ )
+
+ src_content = self.__dict__["_content"]
+ if isinstance(src_content, list):
+ content_copy: List[Optional[Node]] = []
+ for v in src_content:
+ old_parent = v.__dict__["_parent"]
+ try:
+ v.__dict__["_parent"] = None
+ vc = copy.deepcopy(v, memo=memo)
+ vc.__dict__["_parent"] = res
+ content_copy.append(vc)
+ finally:
+ v.__dict__["_parent"] = old_parent
+ else:
+ # None and strings can be assigned as is
+ content_copy = src_content
+
+ res.__dict__["_content"] = content_copy
+ res.__dict__["_parent"] = self.__dict__["_parent"]
+
+ return res
+
+ def copy(self) -> "ListConfig":
+ return copy.copy(self)
+
+ # hide content while inspecting in debugger
+ def __dir__(self) -> Iterable[str]:
+ if self._is_missing() or self._is_none():
+ return []
+ return [str(x) for x in range(0, len(self))]
+
+ def __setattr__(self, key: str, value: Any) -> None:
+ self._format_and_raise(
+ key=key,
+ value=value,
+ cause=ConfigAttributeError("ListConfig does not support attribute access"),
+ )
+ assert False
+
+ def __getattr__(self, key: str) -> Any:
+ # PyCharm is sometimes inspecting __members__, be sure to tell it we don't have that.
+ if key == "__members__":
+ raise AttributeError()
+
+ if key == "__name__":
+ raise AttributeError()
+
+ if is_int(key):
+ return self.__getitem__(int(key))
+ else:
+ self._format_and_raise(
+ key=key,
+ value=None,
+ cause=ConfigAttributeError(
+ "ListConfig does not support attribute access"
+ ),
+ )
+
+ def __getitem__(self, index: Union[int, slice]) -> Any:
+ try:
+ if self._is_missing():
+ raise MissingMandatoryValue("ListConfig is missing")
+ self._validate_get(index, None)
+ if self._is_none():
+ raise TypeError(
+ "ListConfig object representing None is not subscriptable"
+ )
+
+ assert isinstance(self.__dict__["_content"], list)
+ if isinstance(index, slice):
+ result = []
+ start, stop, step = self._correct_index_params(index)
+ for slice_idx in itertools.islice(
+ range(0, len(self)), start, stop, step
+ ):
+ val = self._resolve_with_default(
+ key=slice_idx, value=self.__dict__["_content"][slice_idx]
+ )
+ result.append(val)
+ if index.step and index.step < 0:
+ result.reverse()
+ return result
+ else:
+ return self._resolve_with_default(
+ key=index, value=self.__dict__["_content"][index]
+ )
+ except Exception as e:
+ self._format_and_raise(key=index, value=None, cause=e)
+
+ def _correct_index_params(self, index: slice) -> Tuple[int, int, int]:
+ start = index.start
+ stop = index.stop
+ step = index.step
+ if index.start and index.start < 0:
+ start = self.__len__() + index.start
+ if index.stop and index.stop < 0:
+ stop = self.__len__() + index.stop
+ if index.step and index.step < 0:
+ step = abs(step)
+ if start and stop:
+ if start > stop:
+ start, stop = stop + 1, start + 1
+ else:
+ start = stop = 0
+ elif not start and stop:
+ start = list(range(self.__len__() - 1, stop, -step))[0]
+ stop = None
+ elif start and not stop:
+ stop = start + 1
+ start = (stop - 1) % step
+ else:
+ start = (self.__len__() - 1) % step
+ return start, stop, step
+
+ def _set_at_index(self, index: Union[int, slice], value: Any) -> None:
+ self._set_item_impl(index, value)
+
+ def __setitem__(self, index: Union[int, slice], value: Any) -> None:
+ try:
+ if isinstance(index, slice):
+ _ = iter(value) # check iterable
+ self_indices = index.indices(len(self))
+ indexes = range(*self_indices)
+
+ # Ensure lengths match for extended slice assignment
+ if index.step not in (None, 1):
+ if len(indexes) != len(value):
+ raise ValueError(
+ f"attempt to assign sequence of size {len(value)}"
+ f" to extended slice of size {len(indexes)}"
+ )
+
+ # Initialize insertion offsets for empty slices
+ if len(indexes) == 0:
+ curr_index = self_indices[0] - 1
+ val_i = -1
+
+ work_copy = self.copy() # For atomicity manipulate a copy
+
+ # Delete and optionally replace non empty slices
+ only_removed = 0
+ for val_i, i in enumerate(indexes):
+ curr_index = i - only_removed
+ del work_copy[curr_index]
+ if val_i < len(value):
+ work_copy.insert(curr_index, value[val_i])
+ else:
+ only_removed += 1
+
+ # Insert any remaining input items
+ for val_i in range(val_i + 1, len(value)):
+ curr_index += 1
+ work_copy.insert(curr_index, value[val_i])
+
+ # Reinitialize self with work_copy
+ self.clear()
+ self.extend(work_copy)
+ else:
+ self._set_at_index(index, value)
+ except Exception as e:
+ self._format_and_raise(key=index, value=value, cause=e)
+
+ def append(self, item: Any) -> None:
+ content = self.__dict__["_content"]
+ index = len(content)
+ content.append(None)
+ try:
+ self._set_item_impl(index, item)
+ except Exception as e:
+ del content[index]
+ self._format_and_raise(key=index, value=item, cause=e)
+ assert False
+
+ def _update_keys(self) -> None:
+ for i in range(len(self)):
+ node = self._get_node(i)
+ if node is not None:
+ assert isinstance(node, Node)
+ node._metadata.key = i
+
+ def insert(self, index: int, item: Any) -> None:
+ from omegaconf.omegaconf import _maybe_wrap
+
+ try:
+ if self._get_flag("readonly"):
+ raise ReadonlyConfigError("Cannot insert into a read-only ListConfig")
+ if self._is_none():
+ raise TypeError(
+ "Cannot insert into ListConfig object representing None"
+ )
+ if self._is_missing():
+ raise MissingMandatoryValue("Cannot insert into missing ListConfig")
+
+ try:
+ assert isinstance(self.__dict__["_content"], list)
+ # insert place holder
+ self.__dict__["_content"].insert(index, None)
+ is_optional, ref_type = _resolve_optional(self._metadata.element_type)
+ node = _maybe_wrap(
+ ref_type=ref_type,
+ key=index,
+ value=item,
+ is_optional=is_optional,
+ parent=self,
+ )
+ self._validate_set(key=index, value=node)
+ self._set_at_index(index, node)
+ self._update_keys()
+ except Exception:
+ del self.__dict__["_content"][index]
+ self._update_keys()
+ raise
+ except Exception as e:
+ self._format_and_raise(key=index, value=item, cause=e)
+ assert False
+
+ def extend(self, lst: Iterable[Any]) -> None:
+ assert isinstance(lst, (tuple, list, ListConfig))
+ for x in lst:
+ self.append(x)
+
+ def remove(self, x: Any) -> None:
+ del self[self.index(x)]
+
+ def __delitem__(self, key: Union[int, slice]) -> None:
+ if self._get_flag("readonly"):
+ self._format_and_raise(
+ key=key,
+ value=None,
+ cause=ReadonlyConfigError(
+ "Cannot delete item from read-only ListConfig"
+ ),
+ )
+ del self.__dict__["_content"][key]
+ self._update_keys()
+
+ def clear(self) -> None:
+ del self[:]
+
+ def index(
+ self, x: Any, start: Optional[int] = None, end: Optional[int] = None
+ ) -> int:
+ if start is None:
+ start = 0
+ if end is None:
+ end = len(self)
+ assert start >= 0
+ assert end <= len(self)
+ found_idx = -1
+ for idx in range(start, end):
+ item = self[idx]
+ if x == item:
+ found_idx = idx
+ break
+ if found_idx != -1:
+ return found_idx
+ else:
+ self._format_and_raise(
+ key=None,
+ value=None,
+ cause=ConfigValueError("Item not found in ListConfig"),
+ )
+ assert False
+
+ def count(self, x: Any) -> int:
+ c = 0
+ for item in self:
+ if item == x:
+ c = c + 1
+ return c
+
+ def _get_node(
+ self,
+ key: Union[int, slice],
+ validate_access: bool = True,
+ validate_key: bool = True,
+ throw_on_missing_value: bool = False,
+ throw_on_missing_key: bool = False,
+ ) -> Union[Optional[Node], List[Optional[Node]]]:
+ try:
+ if self._is_none():
+ raise TypeError(
+ "Cannot get_node from a ListConfig object representing None"
+ )
+ if self._is_missing():
+ raise MissingMandatoryValue("Cannot get_node from a missing ListConfig")
+ assert isinstance(self.__dict__["_content"], list)
+ if validate_access:
+ self._validate_get(key)
+
+ value = self.__dict__["_content"][key]
+ if value is not None:
+ if isinstance(key, slice):
+ assert isinstance(value, list)
+ for v in value:
+ if throw_on_missing_value and v._is_missing():
+ raise MissingMandatoryValue("Missing mandatory value")
+ else:
+ assert isinstance(value, Node)
+ if throw_on_missing_value and value._is_missing():
+ raise MissingMandatoryValue("Missing mandatory value: $KEY")
+ return value
+ except (IndexError, TypeError, MissingMandatoryValue, KeyValidationError) as e:
+ if isinstance(e, MissingMandatoryValue) and throw_on_missing_value:
+ raise
+ if validate_access:
+ self._format_and_raise(key=key, value=None, cause=e)
+ assert False
+ else:
+ return None
+
+ def get(self, index: int, default_value: Any = None) -> Any:
+ try:
+ if self._is_none():
+ raise TypeError("Cannot get from a ListConfig object representing None")
+ if self._is_missing():
+ raise MissingMandatoryValue("Cannot get from a missing ListConfig")
+ self._validate_get(index, None)
+ assert isinstance(self.__dict__["_content"], list)
+ return self._resolve_with_default(
+ key=index,
+ value=self.__dict__["_content"][index],
+ default_value=default_value,
+ )
+ except Exception as e:
+ self._format_and_raise(key=index, value=None, cause=e)
+ assert False
+
+ def pop(self, index: int = -1) -> Any:
+ try:
+ if self._get_flag("readonly"):
+ raise ReadonlyConfigError("Cannot pop from read-only ListConfig")
+ if self._is_none():
+ raise TypeError("Cannot pop from a ListConfig object representing None")
+ if self._is_missing():
+ raise MissingMandatoryValue("Cannot pop from a missing ListConfig")
+
+ assert isinstance(self.__dict__["_content"], list)
+ node = self._get_child(index)
+ assert isinstance(node, Node)
+ ret = self._resolve_with_default(key=index, value=node, default_value=None)
+ del self.__dict__["_content"][index]
+ self._update_keys()
+ return ret
+ except KeyValidationError as e:
+ self._format_and_raise(
+ key=index, value=None, cause=e, type_override=ConfigTypeError
+ )
+ assert False
+ except Exception as e:
+ self._format_and_raise(key=index, value=None, cause=e)
+ assert False
+
+ def sort(
+ self, key: Optional[Callable[[Any], Any]] = None, reverse: bool = False
+ ) -> None:
+ try:
+ if self._get_flag("readonly"):
+ raise ReadonlyConfigError("Cannot sort a read-only ListConfig")
+ if self._is_none():
+ raise TypeError("Cannot sort a ListConfig object representing None")
+ if self._is_missing():
+ raise MissingMandatoryValue("Cannot sort a missing ListConfig")
+
+ if key is None:
+
+ def key1(x: Any) -> Any:
+ return x._value()
+
+ else:
+
+ def key1(x: Any) -> Any:
+ return key(x._value()) # type: ignore
+
+ assert isinstance(self.__dict__["_content"], list)
+ self.__dict__["_content"].sort(key=key1, reverse=reverse)
+
+ except Exception as e:
+ self._format_and_raise(key=None, value=None, cause=e)
+ assert False
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, (list, tuple)) or other is None:
+ other = ListConfig(other, flags={"allow_objects": True})
+ return ListConfig._list_eq(self, other)
+ if other is None or isinstance(other, ListConfig):
+ return ListConfig._list_eq(self, other)
+ if self._is_missing():
+ return _is_missing_literal(other)
+ return NotImplemented
+
+ def __ne__(self, other: Any) -> bool:
+ x = self.__eq__(other)
+ if x is not NotImplemented:
+ return not x
+ return NotImplemented
+
+ def __hash__(self) -> int:
+ return hash(str(self))
+
+ def __iter__(self) -> Iterator[Any]:
+ return self._iter_ex(resolve=True)
+
+ class ListIterator(Iterator[Any]):
+ def __init__(self, lst: Any, resolve: bool) -> None:
+ self.resolve = resolve
+ self.iterator = iter(lst.__dict__["_content"])
+ self.index = 0
+ from .nodes import ValueNode
+
+ self.ValueNode = ValueNode
+
+ def __next__(self) -> Any:
+
+ x = next(self.iterator)
+ if self.resolve:
+ x = x._dereference_node()
+ if x._is_missing():
+ raise MissingMandatoryValue(f"Missing value at index {self.index}")
+
+ self.index = self.index + 1
+ if isinstance(x, self.ValueNode):
+ return x._value()
+ else:
+ # Must be omegaconf.Container. not checking for perf reasons.
+ if x._is_none():
+ return None
+ return x
+
+ def __repr__(self) -> str: # pragma: no cover
+ return f"ListConfig.ListIterator(resolve={self.resolve})"
+
+ def _iter_ex(self, resolve: bool) -> Iterator[Any]:
+ try:
+ if self._is_none():
+ raise TypeError("Cannot iterate a ListConfig object representing None")
+ if self._is_missing():
+ raise MissingMandatoryValue("Cannot iterate a missing ListConfig")
+
+ return ListConfig.ListIterator(self, resolve)
+ except (TypeError, MissingMandatoryValue) as e:
+ self._format_and_raise(key=None, value=None, cause=e)
+ assert False
+
+ def __add__(self, other: Union[List[Any], "ListConfig"]) -> "ListConfig":
+ # res is sharing this list's parent to allow interpolation to work as expected
+ res = ListConfig(parent=self._get_parent(), content=[])
+ res.extend(self)
+ res.extend(other)
+ return res
+
+ def __radd__(self, other: Union[List[Any], "ListConfig"]) -> "ListConfig":
+ # res is sharing this list's parent to allow interpolation to work as expected
+ res = ListConfig(parent=self._get_parent(), content=[])
+ res.extend(other)
+ res.extend(self)
+ return res
+
+ def __iadd__(self, other: Iterable[Any]) -> "ListConfig":
+ self.extend(other)
+ return self
+
+ def __contains__(self, item: Any) -> bool:
+ if self._is_none():
+ raise TypeError(
+ "Cannot check if an item is in a ListConfig object representing None"
+ )
+ if self._is_missing():
+ raise MissingMandatoryValue(
+ "Cannot check if an item is in missing ListConfig"
+ )
+
+ lst = self.__dict__["_content"]
+ for x in lst:
+ x = x._dereference_node()
+ if x == item:
+ return True
+ return False
+
+ def _set_value(self, value: Any, flags: Optional[Dict[str, bool]] = None) -> None:
+ try:
+ previous_content = self.__dict__["_content"]
+ previous_metadata = self.__dict__["_metadata"]
+ self._set_value_impl(value, flags)
+ except Exception as e:
+ self.__dict__["_content"] = previous_content
+ self.__dict__["_metadata"] = previous_metadata
+ raise e
+
+ def _set_value_impl(
+ self, value: Any, flags: Optional[Dict[str, bool]] = None
+ ) -> None:
+ from omegaconf import MISSING, flag_override
+
+ if flags is None:
+ flags = {}
+
+ vk = get_value_kind(value, strict_interpolation_validation=True)
+ if _is_none(value):
+ if not self._is_optional():
+ raise ValidationError(
+ "Non optional ListConfig cannot be constructed from None"
+ )
+ self.__dict__["_content"] = None
+ self._metadata.object_type = None
+ elif vk is ValueKind.MANDATORY_MISSING:
+ self.__dict__["_content"] = MISSING
+ self._metadata.object_type = None
+ elif vk == ValueKind.INTERPOLATION:
+ self.__dict__["_content"] = value
+ self._metadata.object_type = None
+ else:
+ if not (is_primitive_list(value) or isinstance(value, ListConfig)):
+ type_ = type(value)
+ msg = f"Invalid value assigned: {type_.__name__} is not a ListConfig, list or tuple."
+ raise ValidationError(msg)
+
+ self.__dict__["_content"] = []
+ if isinstance(value, ListConfig):
+ self._metadata.flags = copy.deepcopy(flags)
+ # disable struct and readonly for the construction phase
+ # retaining other flags like allow_objects. The real flags are restored at the end of this function
+ with flag_override(self, ["struct", "readonly"], False):
+ for item in value._iter_ex(resolve=False):
+ self.append(item)
+ elif is_primitive_list(value):
+ with flag_override(self, ["struct", "readonly"], False):
+ for item in value:
+ self.append(item)
+ self._metadata.object_type = list
+
+ @staticmethod
+ def _list_eq(l1: Optional["ListConfig"], l2: Optional["ListConfig"]) -> bool:
+ l1_none = l1.__dict__["_content"] is None
+ l2_none = l2.__dict__["_content"] is None
+ if l1_none and l2_none:
+ return True
+ if l1_none != l2_none:
+ return False
+
+ assert isinstance(l1, ListConfig)
+ assert isinstance(l2, ListConfig)
+ if len(l1) != len(l2):
+ return False
+ for i in range(len(l1)):
+ if not BaseContainer._item_eq(l1, i, l2, i):
+ return False
+
+ return True
diff --git a/cameras/deps/omegaconf/nodes.py b/cameras/deps/omegaconf/nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f02cde65801fc9b7443595fc80bd8017dd8273b
--- /dev/null
+++ b/cameras/deps/omegaconf/nodes.py
@@ -0,0 +1,545 @@
+import copy
+import math
+import sys
+from abc import abstractmethod
+from enum import Enum
+from pathlib import Path
+from typing import Any, Dict, Optional, Type, Union
+
+from omegaconf._utils import (
+ ValueKind,
+ _is_interpolation,
+ get_type_of,
+ get_value_kind,
+ is_primitive_container,
+ type_str,
+)
+from omegaconf.base import Box, DictKeyType, Metadata, Node
+from omegaconf.errors import ReadonlyConfigError, UnsupportedValueType, ValidationError
+
+
+class ValueNode(Node):
+ _val: Any
+
+ def __init__(self, parent: Optional[Box], value: Any, metadata: Metadata):
+ from omegaconf import read_write
+
+ super().__init__(parent=parent, metadata=metadata)
+ with read_write(self):
+ self._set_value(value) # lgtm [py/init-calls-subclass]
+
+ def _value(self) -> Any:
+ return self._val
+
+ def _set_value(self, value: Any, flags: Optional[Dict[str, bool]] = None) -> None:
+ if self._get_flag("readonly"):
+ raise ReadonlyConfigError("Cannot set value of read-only config node")
+
+ if isinstance(value, str) and get_value_kind(
+ value, strict_interpolation_validation=True
+ ) in (
+ ValueKind.INTERPOLATION,
+ ValueKind.MANDATORY_MISSING,
+ ):
+ self._val = value
+ else:
+ self._val = self.validate_and_convert(value)
+
+ def _strict_validate_type(self, value: Any) -> None:
+ ref_type = self._metadata.ref_type
+ if isinstance(ref_type, type) and type(value) is not ref_type:
+ type_hint = type_str(self._metadata.type_hint)
+ raise ValidationError(
+ f"Value '$VALUE' of type '$VALUE_TYPE' is incompatible with type hint '{type_hint}'"
+ )
+
+ def validate_and_convert(self, value: Any) -> Any:
+ """
+ Validates input and converts to canonical form
+ :param value: input value
+ :return: converted value ("100" may be converted to 100 for example)
+ """
+ if value is None:
+ if self._is_optional():
+ return None
+ ref_type_str = type_str(self._metadata.ref_type)
+ raise ValidationError(
+ f"Incompatible value '{value}' for field of type '{ref_type_str}'"
+ )
+
+ # Subclasses can assume that `value` is not None in
+ # `_validate_and_convert_impl()` and in `_strict_validate_type()`.
+ if self._get_flag("convert") is False:
+ self._strict_validate_type(value)
+ return value
+ else:
+ return self._validate_and_convert_impl(value)
+
+ @abstractmethod
+ def _validate_and_convert_impl(self, value: Any) -> Any:
+ ...
+
+ def __str__(self) -> str:
+ return str(self._val)
+
+ def __repr__(self) -> str:
+ return repr(self._val) if hasattr(self, "_val") else "__INVALID__"
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, AnyNode):
+ return self._val == other._val # type: ignore
+ else:
+ return self._val == other # type: ignore
+
+ def __ne__(self, other: Any) -> bool:
+ x = self.__eq__(other)
+ assert x is not NotImplemented
+ return not x
+
+ def __hash__(self) -> int:
+ return hash(self._val)
+
+ def _deepcopy_impl(self, res: Any, memo: Dict[int, Any]) -> None:
+ res.__dict__["_metadata"] = copy.deepcopy(self._metadata, memo=memo)
+ # shallow copy for value to support non-copyable value
+ res.__dict__["_val"] = self._val
+
+ # parent is retained, but not copied
+ res.__dict__["_parent"] = self._parent
+
+ def _is_optional(self) -> bool:
+ return self._metadata.optional
+
+ def _is_interpolation(self) -> bool:
+ return _is_interpolation(self._value())
+
+ def _get_full_key(self, key: Optional[Union[DictKeyType, int]]) -> str:
+ parent = self._get_parent()
+ if parent is None:
+ if self._metadata.key is None:
+ return ""
+ else:
+ return str(self._metadata.key)
+ else:
+ return parent._get_full_key(self._metadata.key)
+
+
+class AnyNode(ValueNode):
+ def __init__(
+ self,
+ value: Any = None,
+ key: Any = None,
+ parent: Optional[Box] = None,
+ flags: Optional[Dict[str, bool]] = None,
+ ):
+ super().__init__(
+ parent=parent,
+ value=value,
+ metadata=Metadata(
+ ref_type=Any, object_type=None, key=key, optional=True, flags=flags
+ ),
+ )
+
+ def _validate_and_convert_impl(self, value: Any) -> Any:
+ from ._utils import is_primitive_type_annotation
+
+ # allow_objects is internal and not an official API. use at your own risk.
+ # Please be aware that this support is subject to change without notice.
+ # If this is deemed useful and supportable it may become an official API.
+
+ if self._get_flag(
+ "allow_objects"
+ ) is not True and not is_primitive_type_annotation(value):
+ t = get_type_of(value)
+ raise UnsupportedValueType(
+ f"Value '{t.__name__}' is not a supported primitive type"
+ )
+ return value
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "AnyNode":
+ res = AnyNode()
+ self._deepcopy_impl(res, memo)
+ return res
+
+
+class StringNode(ValueNode):
+ def __init__(
+ self,
+ value: Any = None,
+ key: Any = None,
+ parent: Optional[Box] = None,
+ is_optional: bool = True,
+ flags: Optional[Dict[str, bool]] = None,
+ ):
+ super().__init__(
+ parent=parent,
+ value=value,
+ metadata=Metadata(
+ key=key,
+ optional=is_optional,
+ ref_type=str,
+ object_type=str,
+ flags=flags,
+ ),
+ )
+
+ def _validate_and_convert_impl(self, value: Any) -> str:
+ from omegaconf import OmegaConf
+
+ if (
+ OmegaConf.is_config(value)
+ or is_primitive_container(value)
+ or isinstance(value, bytes)
+ ):
+ raise ValidationError("Cannot convert '$VALUE_TYPE' to string: '$VALUE'")
+ return str(value)
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "StringNode":
+ res = StringNode()
+ self._deepcopy_impl(res, memo)
+ return res
+
+
+class PathNode(ValueNode):
+ def __init__(
+ self,
+ value: Any = None,
+ key: Any = None,
+ parent: Optional[Box] = None,
+ is_optional: bool = True,
+ flags: Optional[Dict[str, bool]] = None,
+ ):
+ super().__init__(
+ parent=parent,
+ value=value,
+ metadata=Metadata(
+ key=key,
+ optional=is_optional,
+ ref_type=Path,
+ object_type=Path,
+ flags=flags,
+ ),
+ )
+
+ def _strict_validate_type(self, value: Any) -> None:
+ if not isinstance(value, Path):
+ raise ValidationError(
+ "Value '$VALUE' of type '$VALUE_TYPE' is not an instance of 'pathlib.Path'"
+ )
+
+ def _validate_and_convert_impl(self, value: Any) -> Path:
+ if not isinstance(value, (str, Path)):
+ raise ValidationError(
+ "Value '$VALUE' of type '$VALUE_TYPE' could not be converted to Path"
+ )
+
+ return Path(value)
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "PathNode":
+ res = PathNode()
+ self._deepcopy_impl(res, memo)
+ return res
+
+
+class IntegerNode(ValueNode):
+ def __init__(
+ self,
+ value: Any = None,
+ key: Any = None,
+ parent: Optional[Box] = None,
+ is_optional: bool = True,
+ flags: Optional[Dict[str, bool]] = None,
+ ):
+ super().__init__(
+ parent=parent,
+ value=value,
+ metadata=Metadata(
+ key=key,
+ optional=is_optional,
+ ref_type=int,
+ object_type=int,
+ flags=flags,
+ ),
+ )
+
+ def _validate_and_convert_impl(self, value: Any) -> int:
+ try:
+ if type(value) in (str, int):
+ val = int(value)
+ else:
+ raise ValueError()
+ except ValueError:
+ raise ValidationError(
+ "Value '$VALUE' of type '$VALUE_TYPE' could not be converted to Integer"
+ )
+ return val
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "IntegerNode":
+ res = IntegerNode()
+ self._deepcopy_impl(res, memo)
+ return res
+
+
+class BytesNode(ValueNode):
+ def __init__(
+ self,
+ value: Any = None,
+ key: Any = None,
+ parent: Optional[Box] = None,
+ is_optional: bool = True,
+ flags: Optional[Dict[str, bool]] = None,
+ ):
+ super().__init__(
+ parent=parent,
+ value=value,
+ metadata=Metadata(
+ key=key,
+ optional=is_optional,
+ ref_type=bytes,
+ object_type=bytes,
+ flags=flags,
+ ),
+ )
+
+ def _validate_and_convert_impl(self, value: Any) -> bytes:
+ if not isinstance(value, bytes):
+ raise ValidationError(
+ "Value '$VALUE' of type '$VALUE_TYPE' is not of type 'bytes'"
+ )
+ return value
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "BytesNode":
+ res = BytesNode()
+ self._deepcopy_impl(res, memo)
+ return res
+
+
+class FloatNode(ValueNode):
+ def __init__(
+ self,
+ value: Any = None,
+ key: Any = None,
+ parent: Optional[Box] = None,
+ is_optional: bool = True,
+ flags: Optional[Dict[str, bool]] = None,
+ ):
+ super().__init__(
+ parent=parent,
+ value=value,
+ metadata=Metadata(
+ key=key,
+ optional=is_optional,
+ ref_type=float,
+ object_type=float,
+ flags=flags,
+ ),
+ )
+
+ def _validate_and_convert_impl(self, value: Any) -> float:
+ try:
+ if type(value) in (float, str, int):
+ return float(value)
+ else:
+ raise ValueError()
+ except ValueError:
+ raise ValidationError(
+ "Value '$VALUE' of type '$VALUE_TYPE' could not be converted to Float"
+ )
+
+ def __eq__(self, other: Any) -> bool:
+ if isinstance(other, ValueNode):
+ other_val = other._val
+ else:
+ other_val = other
+ if self._val is None and other is None:
+ return True
+ if self._val is None and other is not None:
+ return False
+ if self._val is not None and other is None:
+ return False
+ nan1 = math.isnan(self._val) if isinstance(self._val, float) else False
+ nan2 = math.isnan(other_val) if isinstance(other_val, float) else False
+ return self._val == other_val or (nan1 and nan2)
+
+ def __hash__(self) -> int:
+ return hash(self._val)
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "FloatNode":
+ res = FloatNode()
+ self._deepcopy_impl(res, memo)
+ return res
+
+
+class BooleanNode(ValueNode):
+ def __init__(
+ self,
+ value: Any = None,
+ key: Any = None,
+ parent: Optional[Box] = None,
+ is_optional: bool = True,
+ flags: Optional[Dict[str, bool]] = None,
+ ):
+ super().__init__(
+ parent=parent,
+ value=value,
+ metadata=Metadata(
+ key=key,
+ optional=is_optional,
+ ref_type=bool,
+ object_type=bool,
+ flags=flags,
+ ),
+ )
+
+ def _validate_and_convert_impl(self, value: Any) -> bool:
+ if isinstance(value, bool):
+ return value
+ if isinstance(value, int):
+ return value != 0
+ elif isinstance(value, str):
+ try:
+ return self._validate_and_convert_impl(int(value))
+ except ValueError as e:
+ if value.lower() in ("yes", "y", "on", "true"):
+ return True
+ elif value.lower() in ("no", "n", "off", "false"):
+ return False
+ else:
+ raise ValidationError(
+ "Value '$VALUE' is not a valid bool (type $VALUE_TYPE)"
+ ).with_traceback(sys.exc_info()[2]) from e
+ else:
+ raise ValidationError(
+ "Value '$VALUE' is not a valid bool (type $VALUE_TYPE)"
+ )
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "BooleanNode":
+ res = BooleanNode()
+ self._deepcopy_impl(res, memo)
+ return res
+
+
+class EnumNode(ValueNode): # lgtm [py/missing-equals] : Intentional.
+ """
+ NOTE: EnumNode is serialized to yaml as a string ("Color.BLUE"), not as a fully qualified yaml type.
+ this means serialization to YAML of a typed config (with EnumNode) will not retain the type of the Enum
+ when loaded.
+ This is intentional, Please open an issue against OmegaConf if you wish to discuss this decision.
+ """
+
+ def __init__(
+ self,
+ enum_type: Type[Enum],
+ value: Optional[Union[Enum, str]] = None,
+ key: Any = None,
+ parent: Optional[Box] = None,
+ is_optional: bool = True,
+ flags: Optional[Dict[str, bool]] = None,
+ ):
+ if not isinstance(enum_type, type) or not issubclass(enum_type, Enum):
+ raise ValidationError(
+ f"EnumNode can only operate on Enum subclasses ({enum_type})"
+ )
+ self.fields: Dict[str, str] = {}
+ self.enum_type: Type[Enum] = enum_type
+ for name, constant in enum_type.__members__.items():
+ self.fields[name] = constant.value
+ super().__init__(
+ parent=parent,
+ value=value,
+ metadata=Metadata(
+ key=key,
+ optional=is_optional,
+ ref_type=enum_type,
+ object_type=enum_type,
+ flags=flags,
+ ),
+ )
+
+ def _strict_validate_type(self, value: Any) -> None:
+ ref_type = self._metadata.ref_type
+ if not isinstance(value, ref_type):
+ type_hint = type_str(self._metadata.type_hint)
+ raise ValidationError(
+ f"Value '$VALUE' of type '$VALUE_TYPE' is incompatible with type hint '{type_hint}'"
+ )
+
+ def _validate_and_convert_impl(self, value: Any) -> Enum:
+ return self.validate_and_convert_to_enum(enum_type=self.enum_type, value=value)
+
+ @staticmethod
+ def validate_and_convert_to_enum(enum_type: Type[Enum], value: Any) -> Enum:
+ if not isinstance(value, (str, int)) and not isinstance(value, enum_type):
+ raise ValidationError(
+ f"Value $VALUE ($VALUE_TYPE) is not a valid input for {enum_type}"
+ )
+
+ if isinstance(value, enum_type):
+ return value
+
+ try:
+ if isinstance(value, (float, bool)):
+ raise ValueError
+
+ if isinstance(value, int):
+ return enum_type(value)
+
+ if isinstance(value, str):
+ prefix = f"{enum_type.__name__}."
+ if value.startswith(prefix):
+ value = value[len(prefix) :]
+ return enum_type[value]
+
+ assert False
+
+ except (ValueError, KeyError) as e:
+ valid = ", ".join([x for x in enum_type.__members__.keys()])
+ raise ValidationError(
+ f"Invalid value '$VALUE', expected one of [{valid}]"
+ ).with_traceback(sys.exc_info()[2]) from e
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "EnumNode":
+ res = EnumNode(enum_type=self.enum_type)
+ self._deepcopy_impl(res, memo)
+ return res
+
+
+class InterpolationResultNode(ValueNode):
+ """
+ Special node type, used to wrap interpolation results.
+ """
+
+ def __init__(
+ self,
+ value: Any,
+ key: Any = None,
+ parent: Optional[Box] = None,
+ flags: Optional[Dict[str, bool]] = None,
+ ):
+ super().__init__(
+ parent=parent,
+ value=value,
+ metadata=Metadata(
+ ref_type=Any, object_type=None, key=key, optional=True, flags=flags
+ ),
+ )
+ # In general we should not try to write into interpolation results.
+ if flags is None or "readonly" not in flags:
+ self._set_flag("readonly", True)
+
+ def _set_value(self, value: Any, flags: Optional[Dict[str, bool]] = None) -> None:
+ if self._get_flag("readonly"):
+ raise ReadonlyConfigError("Cannot set value of read-only config node")
+ self._val = self.validate_and_convert(value)
+
+ def _validate_and_convert_impl(self, value: Any) -> Any:
+ # Interpolation results may be anything.
+ return value
+
+ def __deepcopy__(self, memo: Dict[int, Any]) -> "InterpolationResultNode":
+ # Currently there should be no need to deep-copy such nodes.
+ raise NotImplementedError
+
+ def _is_interpolation(self) -> bool:
+ # The result of an interpolation cannot be itself an interpolation.
+ return False
diff --git a/cameras/deps/omegaconf/omegaconf.py b/cameras/deps/omegaconf/omegaconf.py
new file mode 100644
index 0000000000000000000000000000000000000000..efde14a4e266ede954c479c8809cdb8ab80cb2d5
--- /dev/null
+++ b/cameras/deps/omegaconf/omegaconf.py
@@ -0,0 +1,1157 @@
+"""OmegaConf module"""
+import copy
+import inspect
+import io
+import os
+import pathlib
+import sys
+import warnings
+from collections import defaultdict
+from contextlib import contextmanager
+from enum import Enum
+from textwrap import dedent
+from typing import (
+ IO,
+ Any,
+ Callable,
+ Dict,
+ Generator,
+ Iterable,
+ List,
+ Optional,
+ Set,
+ Tuple,
+ Type,
+ Union,
+ overload,
+)
+
+import yaml
+
+from . import DictConfig, DictKeyType, ListConfig
+from ._utils import (
+ _DEFAULT_MARKER_,
+ _ensure_container,
+ _get_value,
+ format_and_raise,
+ get_dict_key_value_types,
+ get_list_element_type,
+ get_omega_conf_dumper,
+ get_type_of,
+ is_attr_class,
+ is_dataclass,
+ is_dict_annotation,
+ is_int,
+ is_list_annotation,
+ is_primitive_container,
+ is_primitive_dict,
+ is_primitive_list,
+ is_structured_config,
+ is_tuple_annotation,
+ is_union_annotation,
+ nullcontext,
+ split_key,
+ type_str,
+)
+from .base import Box, Container, Node, SCMode, UnionNode
+from .basecontainer import BaseContainer
+from .errors import (
+ MissingMandatoryValue,
+ OmegaConfBaseException,
+ UnsupportedInterpolationType,
+ ValidationError,
+)
+from .nodes import (
+ AnyNode,
+ BooleanNode,
+ BytesNode,
+ EnumNode,
+ FloatNode,
+ IntegerNode,
+ PathNode,
+ StringNode,
+ ValueNode,
+)
+
+MISSING: Any = "???"
+
+Resolver = Callable[..., Any]
+
+
+def II(interpolation: str) -> Any:
+ """
+ Equivalent to ``${interpolation}``
+
+ :param interpolation:
+ :return: input ``${node}`` with type Any
+ """
+ return "${" + interpolation + "}"
+
+
+def SI(interpolation: str) -> Any:
+ """
+ Use this for String interpolation, for example ``"http://${host}:${port}"``
+
+ :param interpolation: interpolation string
+ :return: input interpolation with type ``Any``
+ """
+ return interpolation
+
+
+def register_default_resolvers() -> None:
+ from omegaconf.resolvers import oc
+
+ OmegaConf.register_new_resolver("oc.create", oc.create)
+ OmegaConf.register_new_resolver("oc.decode", oc.decode)
+ OmegaConf.register_new_resolver("oc.deprecated", oc.deprecated)
+ OmegaConf.register_new_resolver("oc.env", oc.env)
+ OmegaConf.register_new_resolver("oc.select", oc.select)
+ OmegaConf.register_new_resolver("oc.dict.keys", oc.dict.keys)
+ OmegaConf.register_new_resolver("oc.dict.values", oc.dict.values)
+
+
+class OmegaConf:
+ """OmegaConf primary class"""
+
+ def __init__(self) -> None:
+ raise NotImplementedError("Use one of the static construction functions")
+
+ @staticmethod
+ def structured(
+ obj: Any,
+ parent: Optional[BaseContainer] = None,
+ flags: Optional[Dict[str, bool]] = None,
+ ) -> Any:
+ return OmegaConf.create(obj, parent, flags)
+
+ @staticmethod
+ @overload
+ def create(
+ obj: str,
+ parent: Optional[BaseContainer] = None,
+ flags: Optional[Dict[str, bool]] = None,
+ ) -> Union[DictConfig, ListConfig]:
+ ...
+
+ @staticmethod
+ @overload
+ def create(
+ obj: Union[List[Any], Tuple[Any, ...]],
+ parent: Optional[BaseContainer] = None,
+ flags: Optional[Dict[str, bool]] = None,
+ ) -> ListConfig:
+ ...
+
+ @staticmethod
+ @overload
+ def create(
+ obj: DictConfig,
+ parent: Optional[BaseContainer] = None,
+ flags: Optional[Dict[str, bool]] = None,
+ ) -> DictConfig:
+ ...
+
+ @staticmethod
+ @overload
+ def create(
+ obj: ListConfig,
+ parent: Optional[BaseContainer] = None,
+ flags: Optional[Dict[str, bool]] = None,
+ ) -> ListConfig:
+ ...
+
+ @staticmethod
+ @overload
+ def create(
+ obj: Optional[Dict[Any, Any]] = None,
+ parent: Optional[BaseContainer] = None,
+ flags: Optional[Dict[str, bool]] = None,
+ ) -> DictConfig:
+ ...
+
+ @staticmethod
+ def create( # noqa F811
+ obj: Any = _DEFAULT_MARKER_,
+ parent: Optional[BaseContainer] = None,
+ flags: Optional[Dict[str, bool]] = None,
+ ) -> Union[DictConfig, ListConfig]:
+ return OmegaConf._create_impl(
+ obj=obj,
+ parent=parent,
+ flags=flags,
+ )
+
+ @staticmethod
+ def load(file_: Union[str, pathlib.Path, IO[Any]]) -> Union[DictConfig, ListConfig]:
+ from ._utils import get_yaml_loader
+
+ if isinstance(file_, (str, pathlib.Path)):
+ with io.open(os.path.abspath(file_), "r", encoding="utf-8") as f:
+ obj = yaml.load(f, Loader=get_yaml_loader())
+ elif getattr(file_, "read", None):
+ obj = yaml.load(file_, Loader=get_yaml_loader())
+ else:
+ raise TypeError("Unexpected file type")
+
+ if obj is not None and not isinstance(obj, (list, dict, str)):
+ raise IOError( # pragma: no cover
+ f"Invalid loaded object type: {type(obj).__name__}"
+ )
+
+ ret: Union[DictConfig, ListConfig]
+ if obj is None:
+ ret = OmegaConf.create()
+ else:
+ ret = OmegaConf.create(obj)
+ return ret
+
+ @staticmethod
+ def save(
+ config: Any, f: Union[str, pathlib.Path, IO[Any]], resolve: bool = False
+ ) -> None:
+ """
+ Save as configuration object to a file
+
+ :param config: omegaconf.Config object (DictConfig or ListConfig).
+ :param f: filename or file object
+ :param resolve: True to save a resolved config (defaults to False)
+ """
+ if is_dataclass(config) or is_attr_class(config):
+ config = OmegaConf.create(config)
+ data = OmegaConf.to_yaml(config, resolve=resolve)
+ if isinstance(f, (str, pathlib.Path)):
+ with io.open(os.path.abspath(f), "w", encoding="utf-8") as file:
+ file.write(data)
+ elif hasattr(f, "write"):
+ f.write(data)
+ f.flush()
+ else:
+ raise TypeError("Unexpected file type")
+
+ @staticmethod
+ def from_cli(args_list: Optional[List[str]] = None) -> DictConfig:
+ if args_list is None:
+ # Skip program name
+ args_list = sys.argv[1:]
+ return OmegaConf.from_dotlist(args_list)
+
+ @staticmethod
+ def from_dotlist(dotlist: List[str]) -> DictConfig:
+ """
+ Creates config from the content sys.argv or from the specified args list of not None
+
+ :param dotlist: A list of dotlist-style strings, e.g. ``["foo.bar=1", "baz=qux"]``.
+ :return: A ``DictConfig`` object created from the dotlist.
+ """
+ conf = OmegaConf.create()
+ conf.merge_with_dotlist(dotlist)
+ return conf
+
+ @staticmethod
+ def merge(
+ *configs: Union[
+ DictConfig,
+ ListConfig,
+ Dict[DictKeyType, Any],
+ List[Any],
+ Tuple[Any, ...],
+ Any,
+ ],
+ ) -> Union[ListConfig, DictConfig]:
+ """
+ Merge a list of previously created configs into a single one
+
+ :param configs: Input configs
+ :return: the merged config object.
+ """
+ assert len(configs) > 0
+ target = copy.deepcopy(configs[0])
+ target = _ensure_container(target)
+ assert isinstance(target, (DictConfig, ListConfig))
+
+ with flag_override(target, "readonly", False):
+ target.merge_with(*configs[1:])
+ turned_readonly = target._get_flag("readonly") is True
+
+ if turned_readonly:
+ OmegaConf.set_readonly(target, True)
+
+ return target
+
+ @staticmethod
+ def unsafe_merge(
+ *configs: Union[
+ DictConfig,
+ ListConfig,
+ Dict[DictKeyType, Any],
+ List[Any],
+ Tuple[Any, ...],
+ Any,
+ ],
+ ) -> Union[ListConfig, DictConfig]:
+ """
+ Merge a list of previously created configs into a single one
+ This is much faster than OmegaConf.merge() as the input configs are not copied.
+ However, the input configs must not be used after this operation as will become inconsistent.
+
+ :param configs: Input configs
+ :return: the merged config object.
+ """
+ assert len(configs) > 0
+ target = configs[0]
+ target = _ensure_container(target)
+ assert isinstance(target, (DictConfig, ListConfig))
+
+ with flag_override(
+ target, ["readonly", "no_deepcopy_set_nodes"], [False, True]
+ ):
+ target.merge_with(*configs[1:])
+ turned_readonly = target._get_flag("readonly") is True
+
+ if turned_readonly:
+ OmegaConf.set_readonly(target, True)
+
+ return target
+
+ @staticmethod
+ def register_resolver(name: str, resolver: Resolver) -> None:
+ warnings.warn(
+ dedent(
+ """\
+ register_resolver() is deprecated.
+ See https://github.com/omry/omegaconf/issues/426 for migration instructions.
+ """
+ ),
+ stacklevel=2,
+ )
+ return OmegaConf.legacy_register_resolver(name, resolver)
+
+ # This function will eventually be deprecated and removed.
+ @staticmethod
+ def legacy_register_resolver(name: str, resolver: Resolver) -> None:
+ assert callable(resolver), "resolver must be callable"
+ # noinspection PyProtectedMember
+ assert (
+ name not in BaseContainer._resolvers
+ ), f"resolver '{name}' is already registered"
+
+ def resolver_wrapper(
+ config: BaseContainer,
+ parent: BaseContainer,
+ node: Node,
+ args: Tuple[Any, ...],
+ args_str: Tuple[str, ...],
+ ) -> Any:
+ cache = OmegaConf.get_cache(config)[name]
+ # "Un-escape " spaces and commas.
+ args_unesc = [x.replace(r"\ ", " ").replace(r"\,", ",") for x in args_str]
+
+ # Nested interpolations behave in a potentially surprising way with
+ # legacy resolvers (they remain as strings, e.g., "${foo}"). If any
+ # input looks like an interpolation we thus raise an exception.
+ try:
+ bad_arg = next(i for i in args_unesc if "${" in i)
+ except StopIteration:
+ pass
+ else:
+ raise ValueError(
+ f"Resolver '{name}' was called with argument '{bad_arg}' that appears "
+ f"to be an interpolation. Nested interpolations are not supported for "
+ f"resolvers registered with `[legacy_]register_resolver()`, please use "
+ f"`register_new_resolver()` instead (see "
+ f"https://github.com/omry/omegaconf/issues/426 for migration instructions)."
+ )
+ key = args_str
+ val = cache[key] if key in cache else resolver(*args_unesc)
+ cache[key] = val
+ return val
+
+ # noinspection PyProtectedMember
+ BaseContainer._resolvers[name] = resolver_wrapper
+
+ @staticmethod
+ def register_new_resolver(
+ name: str,
+ resolver: Resolver,
+ *,
+ replace: bool = False,
+ use_cache: bool = False,
+ ) -> None:
+ """
+ Register a resolver.
+
+ :param name: Name of the resolver.
+ :param resolver: Callable whose arguments are provided in the interpolation,
+ e.g., with ${foo:x,0,${y.z}} these arguments are respectively "x" (str),
+ 0 (int) and the value of ``y.z``.
+ :param replace: If set to ``False`` (default), then a ``ValueError`` is raised if
+ an existing resolver has already been registered with the same name.
+ If set to ``True``, then the new resolver replaces the previous one.
+ NOTE: The cache on existing config objects is not affected, use
+ ``OmegaConf.clear_cache(cfg)`` to clear it.
+ :param use_cache: Whether the resolver's outputs should be cached. The cache is
+ based only on the string literals representing the resolver arguments, e.g.,
+ ${foo:${bar}} will always return the same value regardless of the value of
+ ``bar`` if the cache is enabled for ``foo``.
+ """
+ if not callable(resolver):
+ raise TypeError("resolver must be callable")
+ if not name:
+ raise ValueError("cannot use an empty resolver name")
+
+ if not replace and OmegaConf.has_resolver(name):
+ raise ValueError(f"resolver '{name}' is already registered")
+
+ try:
+ sig: Optional[inspect.Signature] = inspect.signature(resolver)
+ except ValueError:
+ sig = None
+
+ def _should_pass(special: str) -> bool:
+ ret = sig is not None and special in sig.parameters
+ if ret and use_cache:
+ raise ValueError(
+ f"use_cache=True is incompatible with functions that receive the {special}"
+ )
+ return ret
+
+ pass_parent = _should_pass("_parent_")
+ pass_node = _should_pass("_node_")
+ pass_root = _should_pass("_root_")
+
+ def resolver_wrapper(
+ config: BaseContainer,
+ parent: Container,
+ node: Node,
+ args: Tuple[Any, ...],
+ args_str: Tuple[str, ...],
+ ) -> Any:
+ if use_cache:
+ cache = OmegaConf.get_cache(config)[name]
+ try:
+ return cache[args_str]
+ except KeyError:
+ pass
+
+ # Call resolver.
+ kwargs: Dict[str, Node] = {}
+ if pass_parent:
+ kwargs["_parent_"] = parent
+ if pass_node:
+ kwargs["_node_"] = node
+ if pass_root:
+ kwargs["_root_"] = config
+
+ ret = resolver(*args, **kwargs)
+
+ if use_cache:
+ cache[args_str] = ret
+ return ret
+
+ # noinspection PyProtectedMember
+ BaseContainer._resolvers[name] = resolver_wrapper
+
+ @classmethod
+ def has_resolver(cls, name: str) -> bool:
+ return cls._get_resolver(name) is not None
+
+ # noinspection PyProtectedMember
+ @staticmethod
+ def clear_resolvers() -> None:
+ """
+ Clear(remove) all OmegaConf resolvers, then re-register OmegaConf's default resolvers.
+ """
+ BaseContainer._resolvers = {}
+ register_default_resolvers()
+
+ @classmethod
+ def clear_resolver(cls, name: str) -> bool:
+ """
+ Clear(remove) any resolver only if it exists.
+
+ Returns a bool: True if resolver is removed and False if not removed.
+
+ .. warning:
+ This method can remove deafult resolvers as well.
+
+ :param name: Name of the resolver.
+ :return: A bool (``True`` if resolver is removed, ``False`` if not found before removing).
+ """
+ if cls.has_resolver(name):
+ BaseContainer._resolvers.pop(name)
+ return True
+ else:
+ # return False if resolver does not exist
+ return False
+
+ @staticmethod
+ def get_cache(conf: BaseContainer) -> Dict[str, Any]:
+ return conf._metadata.resolver_cache
+
+ @staticmethod
+ def set_cache(conf: BaseContainer, cache: Dict[str, Any]) -> None:
+ conf._metadata.resolver_cache = copy.deepcopy(cache)
+
+ @staticmethod
+ def clear_cache(conf: BaseContainer) -> None:
+ OmegaConf.set_cache(conf, defaultdict(dict, {}))
+
+ @staticmethod
+ def copy_cache(from_config: BaseContainer, to_config: BaseContainer) -> None:
+ OmegaConf.set_cache(to_config, OmegaConf.get_cache(from_config))
+
+ @staticmethod
+ def set_readonly(conf: Node, value: Optional[bool]) -> None:
+ # noinspection PyProtectedMember
+ conf._set_flag("readonly", value)
+
+ @staticmethod
+ def is_readonly(conf: Node) -> Optional[bool]:
+ # noinspection PyProtectedMember
+ return conf._get_flag("readonly")
+
+ @staticmethod
+ def set_struct(conf: Container, value: Optional[bool]) -> None:
+ # noinspection PyProtectedMember
+ conf._set_flag("struct", value)
+
+ @staticmethod
+ def is_struct(conf: Container) -> Optional[bool]:
+ # noinspection PyProtectedMember
+ return conf._get_flag("struct")
+
+ @staticmethod
+ def masked_copy(conf: DictConfig, keys: Union[str, List[str]]) -> DictConfig:
+ """
+ Create a masked copy of of this config that contains a subset of the keys
+
+ :param conf: DictConfig object
+ :param keys: keys to preserve in the copy
+ :return: The masked ``DictConfig`` object.
+ """
+ from .dictconfig import DictConfig
+
+ if not isinstance(conf, DictConfig):
+ raise ValueError("masked_copy is only supported for DictConfig")
+
+ if isinstance(keys, str):
+ keys = [keys]
+ content = {key: value for key, value in conf.items_ex(resolve=False, keys=keys)}
+ return DictConfig(content=content)
+
+ @staticmethod
+ def to_container(
+ cfg: Any,
+ *,
+ resolve: bool = False,
+ throw_on_missing: bool = False,
+ enum_to_str: bool = False,
+ structured_config_mode: SCMode = SCMode.DICT,
+ ) -> Union[Dict[DictKeyType, Any], List[Any], None, str, Any]:
+ """
+ Resursively converts an OmegaConf config to a primitive container (dict or list).
+
+ :param cfg: the config to convert
+ :param resolve: True to resolve all values
+ :param throw_on_missing: When True, raise MissingMandatoryValue if any missing values are present.
+ When False (the default), replace missing values with the string "???" in the output container.
+ :param enum_to_str: True to convert Enum keys and values to strings
+ :param structured_config_mode: Specify how Structured Configs (DictConfigs backed by a dataclass) are handled.
+ - By default (``structured_config_mode=SCMode.DICT``) structured configs are converted to plain dicts.
+ - If ``structured_config_mode=SCMode.DICT_CONFIG``, structured config nodes will remain as DictConfig.
+ - If ``structured_config_mode=SCMode.INSTANTIATE``, this function will instantiate structured configs
+ (DictConfigs backed by a dataclass), by creating an instance of the underlying dataclass.
+
+ See also OmegaConf.to_object.
+ :return: A dict or a list representing this config as a primitive container.
+ """
+ if not OmegaConf.is_config(cfg):
+ raise ValueError(
+ f"Input cfg is not an OmegaConf config object ({type_str(type(cfg))})"
+ )
+
+ return BaseContainer._to_content(
+ cfg,
+ resolve=resolve,
+ throw_on_missing=throw_on_missing,
+ enum_to_str=enum_to_str,
+ structured_config_mode=structured_config_mode,
+ )
+
+ @staticmethod
+ def to_object(cfg: Any) -> Union[Dict[DictKeyType, Any], List[Any], None, str, Any]:
+ """
+ Resursively converts an OmegaConf config to a primitive container (dict or list).
+ Any DictConfig objects backed by dataclasses or attrs classes are instantiated
+ as instances of those backing classes.
+
+ This is an alias for OmegaConf.to_container(..., resolve=True, throw_on_missing=True,
+ structured_config_mode=SCMode.INSTANTIATE)
+
+ :param cfg: the config to convert
+ :return: A dict or a list or dataclass representing this config.
+ """
+ return OmegaConf.to_container(
+ cfg=cfg,
+ resolve=True,
+ throw_on_missing=True,
+ enum_to_str=False,
+ structured_config_mode=SCMode.INSTANTIATE,
+ )
+
+ @staticmethod
+ def is_missing(cfg: Any, key: DictKeyType) -> bool:
+ assert isinstance(cfg, Container)
+ try:
+ node = cfg._get_child(key)
+ if node is None:
+ return False
+ assert isinstance(node, Node)
+ return node._is_missing()
+ except (UnsupportedInterpolationType, KeyError, AttributeError):
+ return False
+
+ @staticmethod
+ def is_interpolation(node: Any, key: Optional[Union[int, str]] = None) -> bool:
+ if key is not None:
+ assert isinstance(node, Container)
+ target = node._get_child(key)
+ else:
+ target = node
+ if target is not None:
+ assert isinstance(target, Node)
+ return target._is_interpolation()
+ return False
+
+ @staticmethod
+ def is_list(obj: Any) -> bool:
+ from . import ListConfig
+
+ return isinstance(obj, ListConfig)
+
+ @staticmethod
+ def is_dict(obj: Any) -> bool:
+ from . import DictConfig
+
+ return isinstance(obj, DictConfig)
+
+ @staticmethod
+ def is_config(obj: Any) -> bool:
+ from . import Container
+
+ return isinstance(obj, Container)
+
+ @staticmethod
+ def get_type(obj: Any, key: Optional[str] = None) -> Optional[Type[Any]]:
+ if key is not None:
+ c = obj._get_child(key)
+ else:
+ c = obj
+ return OmegaConf._get_obj_type(c)
+
+ @staticmethod
+ def select(
+ cfg: Container,
+ key: str,
+ *,
+ default: Any = _DEFAULT_MARKER_,
+ throw_on_resolution_failure: bool = True,
+ throw_on_missing: bool = False,
+ ) -> Any:
+ """
+ :param cfg: Config node to select from
+ :param key: Key to select
+ :param default: Default value to return if key is not found
+ :param throw_on_resolution_failure: Raise an exception if an interpolation
+ resolution error occurs, otherwise return None
+ :param throw_on_missing: Raise an exception if an attempt to select a missing key (with the value '???')
+ is made, otherwise return None
+ :return: selected value or None if not found.
+ """
+ from ._impl import select_value
+
+ try:
+ return select_value(
+ cfg=cfg,
+ key=key,
+ default=default,
+ throw_on_resolution_failure=throw_on_resolution_failure,
+ throw_on_missing=throw_on_missing,
+ )
+ except Exception as e:
+ format_and_raise(node=cfg, key=key, value=None, cause=e, msg=str(e))
+
+ @staticmethod
+ def update(
+ cfg: Container,
+ key: str,
+ value: Any = None,
+ *,
+ merge: bool = True,
+ force_add: bool = False,
+ ) -> None:
+ """
+ Updates a dot separated key sequence to a value
+
+ :param cfg: input config to update
+ :param key: key to update (can be a dot separated path)
+ :param value: value to set, if value if a list or a dict it will be merged or set
+ depending on merge_config_values
+ :param merge: If value is a dict or a list, True (default) to merge
+ into the destination, False to replace the destination.
+ :param force_add: insert the entire path regardless of Struct flag or Structured Config nodes.
+ """
+
+ split = split_key(key)
+ root = cfg
+ for i in range(len(split) - 1):
+ k = split[i]
+ # if next_root is a primitive (string, int etc) replace it with an empty map
+ next_root, key_ = _select_one(root, k, throw_on_missing=False)
+ if not isinstance(next_root, Container):
+ if force_add:
+ with flag_override(root, "struct", False):
+ root[key_] = {}
+ else:
+ root[key_] = {}
+ root = root[key_]
+
+ last = split[-1]
+
+ assert isinstance(
+ root, Container
+ ), f"Unexpected type for root: {type(root).__name__}"
+
+ last_key: Union[str, int] = last
+ if isinstance(root, ListConfig):
+ last_key = int(last)
+
+ ctx = flag_override(root, "struct", False) if force_add else nullcontext()
+ with ctx:
+ if merge and (OmegaConf.is_config(value) or is_primitive_container(value)):
+ assert isinstance(root, BaseContainer)
+ node = root._get_child(last_key)
+ if OmegaConf.is_config(node):
+ assert isinstance(node, BaseContainer)
+ node.merge_with(value)
+ return
+
+ if OmegaConf.is_dict(root):
+ assert isinstance(last_key, str)
+ root.__setattr__(last_key, value)
+ elif OmegaConf.is_list(root):
+ assert isinstance(last_key, int)
+ root.__setitem__(last_key, value)
+ else:
+ assert False
+
+ @staticmethod
+ def to_yaml(cfg: Any, *, resolve: bool = False, sort_keys: bool = False) -> str:
+ """
+ returns a yaml dump of this config object.
+
+ :param cfg: Config object, Structured Config type or instance
+ :param resolve: if True, will return a string with the interpolations resolved, otherwise
+ interpolations are preserved
+ :param sort_keys: If True, will print dict keys in sorted order. default False.
+ :return: A string containing the yaml representation.
+ """
+ cfg = _ensure_container(cfg)
+ container = OmegaConf.to_container(cfg, resolve=resolve, enum_to_str=True)
+ return yaml.dump( # type: ignore
+ container,
+ default_flow_style=False,
+ allow_unicode=True,
+ sort_keys=sort_keys,
+ Dumper=get_omega_conf_dumper(),
+ )
+
+ @staticmethod
+ def resolve(cfg: Container) -> None:
+ """
+ Resolves all interpolations in the given config object in-place.
+
+ :param cfg: An OmegaConf container (DictConfig, ListConfig)
+ Raises a ValueError if the input object is not an OmegaConf container.
+ """
+ import omegaconf._impl
+
+ if not OmegaConf.is_config(cfg):
+ # Since this function is mutating the input object in-place, it doesn't make sense to
+ # auto-convert the input object to an OmegaConf container
+ raise ValueError(
+ f"Invalid config type ({type(cfg).__name__}), expected an OmegaConf Container"
+ )
+ omegaconf._impl._resolve(cfg)
+
+ @staticmethod
+ def missing_keys(cfg: Any) -> Set[str]:
+ """
+ Returns a set of missing keys in a dotlist style.
+
+ :param cfg: An ``OmegaConf.Container``,
+ or a convertible object via ``OmegaConf.create`` (dict, list, ...).
+ :return: set of strings of the missing keys.
+ :raises ValueError: On input not representing a config.
+ """
+ cfg = _ensure_container(cfg)
+ missings: Set[str] = set()
+
+ def gather(_cfg: Container) -> None:
+ itr: Iterable[Any]
+ if isinstance(_cfg, ListConfig):
+ itr = range(len(_cfg))
+ else:
+ itr = _cfg
+
+ for key in itr:
+ if OmegaConf.is_missing(_cfg, key):
+ missings.add(_cfg._get_full_key(key))
+ elif OmegaConf.is_config(_cfg[key]):
+ gather(_cfg[key])
+
+ gather(cfg)
+ return missings
+
+ # === private === #
+
+ @staticmethod
+ def _create_impl( # noqa F811
+ obj: Any = _DEFAULT_MARKER_,
+ parent: Optional[BaseContainer] = None,
+ flags: Optional[Dict[str, bool]] = None,
+ ) -> Union[DictConfig, ListConfig]:
+ try:
+ from ._utils import get_yaml_loader
+ from .dictconfig import DictConfig
+ from .listconfig import ListConfig
+
+ if obj is _DEFAULT_MARKER_:
+ obj = {}
+ if isinstance(obj, str):
+ obj = yaml.load(obj, Loader=get_yaml_loader())
+ if obj is None:
+ return OmegaConf.create({}, parent=parent, flags=flags)
+ elif isinstance(obj, str):
+ return OmegaConf.create({obj: None}, parent=parent, flags=flags)
+ else:
+ assert isinstance(obj, (list, dict))
+ return OmegaConf.create(obj, parent=parent, flags=flags)
+
+ else:
+ if (
+ is_primitive_dict(obj)
+ or OmegaConf.is_dict(obj)
+ or is_structured_config(obj)
+ or obj is None
+ ):
+ if isinstance(obj, DictConfig):
+ return DictConfig(
+ content=obj,
+ parent=parent,
+ ref_type=obj._metadata.ref_type,
+ is_optional=obj._metadata.optional,
+ key_type=obj._metadata.key_type,
+ element_type=obj._metadata.element_type,
+ flags=flags,
+ )
+ else:
+ obj_type = OmegaConf.get_type(obj)
+ key_type, element_type = get_dict_key_value_types(obj_type)
+ return DictConfig(
+ content=obj,
+ parent=parent,
+ key_type=key_type,
+ element_type=element_type,
+ flags=flags,
+ )
+ elif is_primitive_list(obj) or OmegaConf.is_list(obj):
+ if isinstance(obj, ListConfig):
+ return ListConfig(
+ content=obj,
+ parent=parent,
+ element_type=obj._metadata.element_type,
+ ref_type=obj._metadata.ref_type,
+ is_optional=obj._metadata.optional,
+ flags=flags,
+ )
+ else:
+ obj_type = OmegaConf.get_type(obj)
+ element_type = get_list_element_type(obj_type)
+ return ListConfig(
+ content=obj,
+ parent=parent,
+ element_type=element_type,
+ ref_type=Any,
+ is_optional=True,
+ flags=flags,
+ )
+ else:
+ if isinstance(obj, type):
+ raise ValidationError(
+ f"Input class '{obj.__name__}' is not a structured config. "
+ "did you forget to decorate it as a dataclass?"
+ )
+ else:
+ raise ValidationError(
+ f"Object of unsupported type: '{type(obj).__name__}'"
+ )
+ except OmegaConfBaseException as e:
+ format_and_raise(node=None, key=None, value=None, msg=str(e), cause=e)
+ assert False
+
+ @staticmethod
+ def _get_obj_type(c: Any) -> Optional[Type[Any]]:
+ if is_structured_config(c):
+ return get_type_of(c)
+ elif c is None:
+ return None
+ elif isinstance(c, DictConfig):
+ if c._is_none():
+ return None
+ elif c._is_missing():
+ return None
+ else:
+ if is_structured_config(c._metadata.object_type):
+ return c._metadata.object_type
+ else:
+ return dict
+ elif isinstance(c, ListConfig):
+ return list
+ elif isinstance(c, ValueNode):
+ return type(c._value())
+ elif isinstance(c, UnionNode):
+ return type(_get_value(c))
+ elif isinstance(c, dict):
+ return dict
+ elif isinstance(c, (list, tuple)):
+ return list
+ else:
+ return get_type_of(c)
+
+ @staticmethod
+ def _get_resolver(
+ name: str,
+ ) -> Optional[
+ Callable[
+ [Container, Container, Node, Tuple[Any, ...], Tuple[str, ...]],
+ Any,
+ ]
+ ]:
+ # noinspection PyProtectedMember
+ return (
+ BaseContainer._resolvers[name] if name in BaseContainer._resolvers else None
+ )
+
+
+# register all default resolvers
+register_default_resolvers()
+
+
+@contextmanager
+def flag_override(
+ config: Node,
+ names: Union[List[str], str],
+ values: Union[List[Optional[bool]], Optional[bool]],
+) -> Generator[Node, None, None]:
+ if isinstance(names, str):
+ names = [names]
+ if values is None or isinstance(values, bool):
+ values = [values]
+
+ prev_states = [config._get_node_flag(name) for name in names]
+
+ try:
+ config._set_flag(names, values)
+ yield config
+ finally:
+ config._set_flag(names, prev_states)
+
+
+@contextmanager
+def read_write(config: Node) -> Generator[Node, None, None]:
+ prev_state = config._get_node_flag("readonly")
+ try:
+ OmegaConf.set_readonly(config, False)
+ yield config
+ finally:
+ OmegaConf.set_readonly(config, prev_state)
+
+
+@contextmanager
+def open_dict(config: Container) -> Generator[Container, None, None]:
+ prev_state = config._get_node_flag("struct")
+ try:
+ OmegaConf.set_struct(config, False)
+ yield config
+ finally:
+ OmegaConf.set_struct(config, prev_state)
+
+
+# === private === #
+
+
+def _node_wrap(
+ parent: Optional[Box],
+ is_optional: bool,
+ value: Any,
+ key: Any,
+ ref_type: Any = Any,
+) -> Node:
+ node: Node
+ if is_dict_annotation(ref_type) or (is_primitive_dict(value) and ref_type is Any):
+ key_type, element_type = get_dict_key_value_types(ref_type)
+ node = DictConfig(
+ content=value,
+ key=key,
+ parent=parent,
+ ref_type=ref_type,
+ is_optional=is_optional,
+ key_type=key_type,
+ element_type=element_type,
+ )
+ elif (is_list_annotation(ref_type) or is_tuple_annotation(ref_type)) or (
+ type(value) in (list, tuple) and ref_type is Any
+ ):
+ element_type = get_list_element_type(ref_type)
+ node = ListConfig(
+ content=value,
+ key=key,
+ parent=parent,
+ is_optional=is_optional,
+ element_type=element_type,
+ ref_type=ref_type,
+ )
+ elif is_structured_config(ref_type) or is_structured_config(value):
+ key_type, element_type = get_dict_key_value_types(value)
+ node = DictConfig(
+ ref_type=ref_type,
+ is_optional=is_optional,
+ content=value,
+ key=key,
+ parent=parent,
+ key_type=key_type,
+ element_type=element_type,
+ )
+ elif is_union_annotation(ref_type):
+ node = UnionNode(
+ content=value,
+ ref_type=ref_type,
+ is_optional=is_optional,
+ key=key,
+ parent=parent,
+ )
+ elif ref_type == Any or ref_type is None:
+ node = AnyNode(value=value, key=key, parent=parent)
+ elif isinstance(ref_type, type) and issubclass(ref_type, Enum):
+ node = EnumNode(
+ enum_type=ref_type,
+ value=value,
+ key=key,
+ parent=parent,
+ is_optional=is_optional,
+ )
+ elif ref_type == int:
+ node = IntegerNode(value=value, key=key, parent=parent, is_optional=is_optional)
+ elif ref_type == float:
+ node = FloatNode(value=value, key=key, parent=parent, is_optional=is_optional)
+ elif ref_type == bool:
+ node = BooleanNode(value=value, key=key, parent=parent, is_optional=is_optional)
+ elif ref_type == str:
+ node = StringNode(value=value, key=key, parent=parent, is_optional=is_optional)
+ elif ref_type == bytes:
+ node = BytesNode(value=value, key=key, parent=parent, is_optional=is_optional)
+ elif ref_type == pathlib.Path:
+ node = PathNode(value=value, key=key, parent=parent, is_optional=is_optional)
+ else:
+ if parent is not None and parent._get_flag("allow_objects") is True:
+ if type(value) in (list, tuple):
+ node = ListConfig(
+ content=value,
+ key=key,
+ parent=parent,
+ ref_type=ref_type,
+ is_optional=is_optional,
+ )
+ elif is_primitive_dict(value):
+ node = DictConfig(
+ content=value,
+ key=key,
+ parent=parent,
+ ref_type=ref_type,
+ is_optional=is_optional,
+ )
+ else:
+ node = AnyNode(value=value, key=key, parent=parent)
+ else:
+ raise ValidationError(f"Unexpected type annotation: {type_str(ref_type)}")
+ return node
+
+
+def _maybe_wrap(
+ ref_type: Any,
+ key: Any,
+ value: Any,
+ is_optional: bool,
+ parent: Optional[BaseContainer],
+) -> Node:
+ # if already a node, update key and parent and return as is.
+ # NOTE: that this mutate the input node!
+ if isinstance(value, Node):
+ value._set_key(key)
+ value._set_parent(parent)
+ return value
+ else:
+ return _node_wrap(
+ ref_type=ref_type,
+ parent=parent,
+ is_optional=is_optional,
+ value=value,
+ key=key,
+ )
+
+
+def _select_one(
+ c: Container, key: str, throw_on_missing: bool, throw_on_type_error: bool = True
+) -> Tuple[Optional[Node], Union[str, int]]:
+ from .dictconfig import DictConfig
+ from .listconfig import ListConfig
+
+ ret_key: Union[str, int] = key
+ assert isinstance(c, Container), f"Unexpected type: {c}"
+ if c._is_none():
+ return None, ret_key
+
+ if isinstance(c, DictConfig):
+ assert isinstance(ret_key, str)
+ val = c._get_child(ret_key, validate_access=False)
+ elif isinstance(c, ListConfig):
+ assert isinstance(ret_key, str)
+ if not is_int(ret_key):
+ if throw_on_type_error:
+ raise TypeError(
+ f"Index '{ret_key}' ({type(ret_key).__name__}) is not an int"
+ )
+ else:
+ val = None
+ else:
+ ret_key = int(ret_key)
+ if ret_key < 0 or ret_key + 1 > len(c):
+ val = None
+ else:
+ val = c._get_child(ret_key)
+ else:
+ assert False
+
+ if val is not None:
+ assert isinstance(val, Node)
+ if val._is_missing():
+ if throw_on_missing:
+ raise MissingMandatoryValue(
+ f"Missing mandatory value: {c._get_full_key(ret_key)}"
+ )
+ else:
+ return val, ret_key
+
+ assert val is None or isinstance(val, Node)
+ return val, ret_key
diff --git a/cameras/deps/omegaconf/py.typed b/cameras/deps/omegaconf/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/cameras/deps/omegaconf/resolvers/__init__.py b/cameras/deps/omegaconf/resolvers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b0bb71168722281af5ddecb46f79e36f63c71e4
--- /dev/null
+++ b/cameras/deps/omegaconf/resolvers/__init__.py
@@ -0,0 +1,5 @@
+from omegaconf.resolvers import oc
+
+__all__ = [
+ "oc",
+]
diff --git a/cameras/deps/omegaconf/resolvers/oc/__init__.py b/cameras/deps/omegaconf/resolvers/oc/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c177ab4f1b35dfa5502a9b6359439bfca180ba1d
--- /dev/null
+++ b/cameras/deps/omegaconf/resolvers/oc/__init__.py
@@ -0,0 +1,113 @@
+import os
+import string
+import warnings
+from typing import Any, Optional
+
+from omegaconf import Container, Node
+from omegaconf._utils import _DEFAULT_MARKER_, _get_value
+from omegaconf.basecontainer import BaseContainer
+from omegaconf.errors import ConfigKeyError
+from omegaconf.grammar_parser import parse
+from omegaconf.resolvers.oc import dict
+
+
+def create(obj: Any, _parent_: Container) -> Any:
+ """Create a config object from `obj`, similar to `OmegaConf.create`"""
+ from omegaconf import OmegaConf
+
+ assert isinstance(_parent_, BaseContainer)
+ return OmegaConf.create(obj, parent=_parent_)
+
+
+def env(key: str, default: Any = _DEFAULT_MARKER_) -> Optional[str]:
+ """
+ :param key: Environment variable key
+ :param default: Optional default value to use in case the key environment variable is not set.
+ If default is not a string, it is converted with str(default).
+ None default is returned as is.
+ :return: The environment variable 'key'. If the environment variable is not set and a default is
+ provided, the default is used. If used, the default is converted to a string with str(default).
+ If the default is None, None is returned (without a string conversion).
+ """
+ try:
+ return os.environ[key]
+ except KeyError:
+ if default is not _DEFAULT_MARKER_:
+ return str(default) if default is not None else None
+ else:
+ raise KeyError(f"Environment variable '{key}' not found")
+
+
+def decode(expr: Optional[str], _parent_: Container, _node_: Node) -> Any:
+ """
+ Parse and evaluate `expr` according to the `singleElement` rule of the grammar.
+
+ If `expr` is `None`, then return `None`.
+ """
+ if expr is None:
+ return None
+
+ if not isinstance(expr, str):
+ raise TypeError(
+ f"`oc.decode` can only take strings or None as input, "
+ f"but `{expr}` is of type {type(expr).__name__}"
+ )
+
+ parse_tree = parse(expr, parser_rule="singleElement", lexer_mode="VALUE_MODE")
+ val = _parent_.resolve_parse_tree(parse_tree, node=_node_)
+ return _get_value(val)
+
+
+def deprecated(
+ key: str,
+ message: str = "'$OLD_KEY' is deprecated. Change your code and config to use '$NEW_KEY'",
+ *,
+ _parent_: Container,
+ _node_: Node,
+) -> Any:
+ from omegaconf._impl import select_node
+
+ if not isinstance(key, str):
+ raise TypeError(
+ f"oc.deprecated: interpolation key type is not a string ({type(key).__name__})"
+ )
+
+ if not isinstance(message, str):
+ raise TypeError(
+ f"oc.deprecated: interpolation message type is not a string ({type(message).__name__})"
+ )
+
+ full_key = _node_._get_full_key(key=None)
+ target_node = select_node(_parent_, key, absolute_key=True)
+ if target_node is None:
+ raise ConfigKeyError(
+ f"In oc.deprecated resolver at '{full_key}': Key not found: '{key}'"
+ )
+ new_key = target_node._get_full_key(key=None)
+ msg = string.Template(message).safe_substitute(
+ OLD_KEY=full_key,
+ NEW_KEY=new_key,
+ )
+ warnings.warn(category=UserWarning, message=msg)
+ return target_node
+
+
+def select(
+ key: str,
+ default: Any = _DEFAULT_MARKER_,
+ *,
+ _parent_: Container,
+) -> Any:
+ from omegaconf._impl import select_value
+
+ return select_value(cfg=_parent_, key=key, absolute_key=True, default=default)
+
+
+__all__ = [
+ "create",
+ "decode",
+ "deprecated",
+ "dict",
+ "env",
+ "select",
+]
diff --git a/cameras/deps/omegaconf/resolvers/oc/dict.py b/cameras/deps/omegaconf/resolvers/oc/dict.py
new file mode 100644
index 0000000000000000000000000000000000000000..adbfd16ac47f2dced46f509c8dfdc8516ba97f4a
--- /dev/null
+++ b/cameras/deps/omegaconf/resolvers/oc/dict.py
@@ -0,0 +1,83 @@
+from typing import Any, List
+
+from omegaconf import AnyNode, Container, DictConfig, ListConfig
+from omegaconf._utils import Marker
+from omegaconf.basecontainer import BaseContainer
+from omegaconf.errors import ConfigKeyError
+
+_DEFAULT_SELECT_MARKER_: Any = Marker("_DEFAULT_SELECT_MARKER_")
+
+
+def keys(
+ key: str,
+ _parent_: Container,
+) -> ListConfig:
+ from omegaconf import OmegaConf
+
+ assert isinstance(_parent_, BaseContainer)
+
+ in_dict = _get_and_validate_dict_input(
+ key, parent=_parent_, resolver_name="oc.dict.keys"
+ )
+
+ ret = OmegaConf.create(list(in_dict.keys()), parent=_parent_)
+ assert isinstance(ret, ListConfig)
+ return ret
+
+
+def values(key: str, _root_: BaseContainer, _parent_: Container) -> ListConfig:
+ assert isinstance(_parent_, BaseContainer)
+ in_dict = _get_and_validate_dict_input(
+ key, parent=_parent_, resolver_name="oc.dict.values"
+ )
+
+ content = in_dict._content
+ assert isinstance(content, dict)
+
+ ret = ListConfig([])
+ if key.startswith("."):
+ key = f".{key}" # extra dot to compensate for extra level of nesting within ret ListConfig
+ for k in content:
+ ref_node = AnyNode(f"${{{key}.{k!s}}}")
+ ret.append(ref_node)
+
+ # Finalize result by setting proper type and parent.
+ element_type: Any = in_dict._metadata.element_type
+ ret._metadata.element_type = element_type
+ ret._metadata.ref_type = List[element_type]
+ ret._set_parent(_parent_)
+
+ return ret
+
+
+def _get_and_validate_dict_input(
+ key: str,
+ parent: BaseContainer,
+ resolver_name: str,
+) -> DictConfig:
+ from omegaconf._impl import select_value
+
+ if not isinstance(key, str):
+ raise TypeError(
+ f"`{resolver_name}` requires a string as input, but obtained `{key}` "
+ f"of type: {type(key).__name__}"
+ )
+
+ in_dict = select_value(
+ parent,
+ key,
+ throw_on_missing=True,
+ absolute_key=True,
+ default=_DEFAULT_SELECT_MARKER_,
+ )
+
+ if in_dict is _DEFAULT_SELECT_MARKER_:
+ raise ConfigKeyError(f"Key not found: '{key}'")
+
+ if not isinstance(in_dict, DictConfig):
+ raise TypeError(
+ f"`{resolver_name}` cannot be applied to objects of type: "
+ f"{type(in_dict).__name__}"
+ )
+
+ return in_dict
diff --git a/cameras/deps/omegaconf/version.py b/cameras/deps/omegaconf/version.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4989b2ef4670f866a92767403c3f63204a06f81
--- /dev/null
+++ b/cameras/deps/omegaconf/version.py
@@ -0,0 +1,13 @@
+import sys # pragma: no cover
+
+__version__ = "2.3.0"
+
+msg = """OmegaConf 2.0 and above is compatible with Python 3.6 and newer.
+You have the following options:
+1. Upgrade to Python 3.6 or newer.
+ This is highly recommended. new features will not be added to OmegaConf 1.4.
+2. Continue using OmegaConf 1.4:
+ You can pip install 'OmegaConf<1.5' to do that.
+"""
+if sys.version_info < (3, 6):
+ raise ImportError(msg) # pragma: no cover
diff --git a/cameras/deps/pydevd_plugins/__init__.py b/cameras/deps/pydevd_plugins/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e4abcfa0ee1e0dc920a8ac4d560c47e43f8ce00e
--- /dev/null
+++ b/cameras/deps/pydevd_plugins/__init__.py
@@ -0,0 +1,6 @@
+try:
+ __import__("pkg_resources").declare_namespace(__name__)
+except ImportError: # pragma: no cover
+ import pkgutil
+
+ __path__ = pkgutil.extend_path(__path__, __name__)
diff --git a/cameras/deps/pydevd_plugins/extensions/__init__.py b/cameras/deps/pydevd_plugins/extensions/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e4abcfa0ee1e0dc920a8ac4d560c47e43f8ce00e
--- /dev/null
+++ b/cameras/deps/pydevd_plugins/extensions/__init__.py
@@ -0,0 +1,6 @@
+try:
+ __import__("pkg_resources").declare_namespace(__name__)
+except ImportError: # pragma: no cover
+ import pkgutil
+
+ __path__ = pkgutil.extend_path(__path__, __name__)
diff --git a/cameras/deps/pydevd_plugins/extensions/pydevd_plugin_omegaconf.py b/cameras/deps/pydevd_plugins/extensions/pydevd_plugin_omegaconf.py
new file mode 100644
index 0000000000000000000000000000000000000000..3dc3ce157b5de32df787e4925fd9251a65fa01d2
--- /dev/null
+++ b/cameras/deps/pydevd_plugins/extensions/pydevd_plugin_omegaconf.py
@@ -0,0 +1,126 @@
+# based on https://github.com/fabioz/PyDev.Debugger/tree/main/pydevd_plugins/extensions
+import os
+import sys
+from typing import Any, Dict
+
+from _pydevd_bundle.pydevd_extension_api import ( # type: ignore
+ StrPresentationProvider,
+ TypeResolveProvider,
+)
+
+DEBUG = False
+
+
+def print_debug(msg: str) -> None: # pragma: no cover
+ if DEBUG:
+ print(msg)
+
+
+def find_mod_attr(mod_name: str, attr: str) -> Any:
+ mod = sys.modules.get(mod_name)
+ return getattr(mod, attr, None)
+
+
+class OmegaConfDeveloperResolver(object):
+ def can_provide(self, type_object: Any, type_name: str) -> bool:
+ Node = find_mod_attr("omegaconf", "Node")
+ return Node is not None and issubclass(type_object, Node)
+
+ def resolve(self, obj: Any, attribute: str) -> Any:
+ return getattr(obj, attribute)
+
+ def get_dictionary(self, obj: Any) -> Any:
+ return obj.__dict__
+
+
+class OmegaConfUserResolver(StrPresentationProvider): # type: ignore
+ def __init__(self) -> None:
+ self.Node = find_mod_attr("omegaconf", "Node")
+ self.ValueNode = find_mod_attr("omegaconf", "ValueNode")
+ self.ListConfig = find_mod_attr("omegaconf", "ListConfig")
+ self.DictConfig = find_mod_attr("omegaconf", "DictConfig")
+ self.InterpolationResolutionError = find_mod_attr(
+ "omegaconf.errors", "InterpolationResolutionError"
+ )
+
+ def can_provide(self, type_object: Any, type_name: str) -> bool:
+ return self.Node is not None and issubclass(type_object, self.Node)
+
+ def resolve(self, obj: Any, attribute: Any) -> Any:
+ if isinstance(obj, self.ListConfig) and isinstance(attribute, str):
+ attribute = int(attribute)
+
+ if isinstance(obj, self.Node):
+ obj = obj._dereference_node()
+
+ val = obj.__dict__["_content"][attribute]
+
+ print_debug(
+ f"resolving {obj} ({type(obj).__name__}), {attribute} -> {val} ({type(val).__name__})"
+ )
+
+ return val
+
+ def _is_simple_value(self, val: Any) -> bool:
+ return (
+ isinstance(val, self.ValueNode)
+ and not val._is_none()
+ and not val._is_missing()
+ and not val._is_interpolation()
+ )
+
+ def get_dictionary(self, obj: Any) -> Dict[str, Any]:
+ d = self._get_dictionary(obj)
+ print_debug(f"get_dictionary {obj}, ({type(obj).__name__}) -> {d}")
+ return d
+
+ def _get_dictionary(self, obj: Any) -> Dict[str, Any]:
+ if isinstance(obj, self.Node):
+ obj = obj._maybe_dereference_node()
+ if obj is None or obj._is_none() or obj._is_missing():
+ return {}
+
+ if isinstance(obj, self.DictConfig):
+ d = {}
+ for k, v in obj.__dict__["_content"].items():
+ if self._is_simple_value(v):
+ v = v._value()
+ d[k] = v
+ elif isinstance(obj, self.ListConfig):
+ d = {}
+ for idx, v in enumerate(obj.__dict__["_content"]):
+ if self._is_simple_value(v):
+ v = v._value()
+ d[str(idx)] = v
+ else:
+ d = {}
+
+ return d
+
+ def get_str(self, val: Any) -> str:
+ if val._is_missing():
+ return "??? "
+ if val._is_interpolation():
+ try:
+ dr = val._dereference_node()
+ except self.InterpolationResolutionError as e:
+ dr = f"ERR: {e}"
+ return f"{val._value()} -> {dr}"
+ else:
+ return f"{val}"
+
+
+# OC_PYDEVD_RESOLVER env can take:
+# DISABLE: Do not install a pydevd resolver
+# USER: Install a resolver for OmegaConf users (default)
+# DEV: Install a resolver for OmegaConf developers. Shows underlying data-model in the debugger.
+resolver = os.environ.get("OC_PYDEVD_RESOLVER", "USER").upper()
+if resolver != "DISABLE": # pragma: no cover
+ if resolver == "USER":
+ TypeResolveProvider.register(OmegaConfUserResolver)
+ elif resolver == "DEV":
+ TypeResolveProvider.register(OmegaConfDeveloperResolver)
+ else:
+ sys.stderr.write(
+ f"OmegaConf pydev plugin: Not installing. Unknown mode {resolver}. Supported one of [USER, DEV, DISABLE]\n"
+ )
diff --git a/cameras/model.safetensors b/cameras/model.safetensors
new file mode 100644
index 0000000000000000000000000000000000000000..3b07f4ca8997261668cb415e7a218da915afda2c
--- /dev/null
+++ b/cameras/model.safetensors
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e01067dc1659613083d9145a9a2547ccdbe6ccbbf83c4fe7b3e8a4e2bdae78b5
+size 541518028
diff --git a/cameras/source/LICENSE b/cameras/source/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..a2fa36578f5094f56f4b0ec57e70eb30aced4519
--- /dev/null
+++ b/cameras/source/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on
+ the same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2025 The Depth Anything 3 Team
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/cameras/source/README.md b/cameras/source/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..9ef877b6ddf6d86ed58566183e79ac74ec00284c
--- /dev/null
+++ b/cameras/source/README.md
@@ -0,0 +1,328 @@
+
+
Depth Anything 3: Recovering the Visual Space from Any Views
+
+This work presents **Depth Anything 3 (DA3)**, a model that predicts spatially consistent geometry from
+arbitrary visual inputs, with or without known camera poses.
+In pursuit of minimal modeling, DA3 yields two key insights:
+- 💎 A **single plain transformer** (e.g., vanilla DINO encoder) is sufficient as a backbone without architectural specialization,
+- ✨ A singular **depth-ray representation** obviates the need for complex multi-task learning.
+
+🏆 DA3 significantly outperforms
+[DA2](https://github.com/DepthAnything/Depth-Anything-V2) for monocular depth estimation,
+and [VGGT](https://github.com/facebookresearch/vggt) for multi-view depth estimation and pose estimation.
+All models are trained exclusively on **public academic datasets**.
+
+
+
+
+
+
+
+
+
+
+## 📰 News
+- **11-12-2025:** 🚀 New models and [**DA3-Streaming**](da3_streaming/README.md) released! Handle ultra-long video sequence inference with less than 12GB GPU memory via sliding-window streaming inference. Special thanks to [Kai Deng](https://github.com/DengKaiCQ) for his contribution to DA3-Streaming!
+- **08-12-2025:** 📊 [Benchmark evaluation pipeline](docs/BENCHMARK.md) released! Evaluate pose estimation & 3D reconstruction on 5 datasets.
+- **30-11-2025:** Add [`use_ray_pose`](#use-ray-pose) and [`ref_view_strategy`](docs/funcs/ref_view_strategy.md) (reference view selection for multi-view inputs).
+- **25-11-2025:** Add [Awesome DA3 Projects](#-awesome-da3-projects), a community-driven section featuring DA3-based applications.
+- **14-11-2025:** Paper, project page, code and models are all released.
+
+## ✨ Highlights
+
+### 🏆 Model Zoo
+We release three series of models, each tailored for specific use cases in visual geometry.
+
+- 🌟 **DA3 Main Series** (`DA3-Giant`, `DA3-Large`, `DA3-Base`, `DA3-Small`) These are our flagship foundation models, trained with a unified depth-ray representation. By varying the input configuration, a single model can perform a wide range of tasks:
+ + 🌊 **Monocular Depth Estimation**: Predicts a depth map from a single RGB image.
+ + 🌊 **Multi-View Depth Estimation**: Generates consistent depth maps from multiple images for high-quality fusion.
+ + 🎯 **Pose-Conditioned Depth Estimation**: Achieves superior depth consistency when camera poses are provided as input.
+ + 📷 **Camera Pose Estimation**: Estimates camera extrinsics and intrinsics from one or more images.
+ + 🟡 **3D Gaussian Estimation**: Directly predicts 3D Gaussians, enabling high-fidelity novel view synthesis.
+
+- 📐 **DA3 Metric Series** (`DA3Metric-Large`) A specialized model fine-tuned for metric depth estimation in monocular settings, ideal for applications requiring real-world scale.
+
+- 🔍 **DA3 Monocular Series** (`DA3Mono-Large`). A dedicated model for high-quality relative monocular depth estimation. Unlike disparity-based models (e.g., [Depth Anything 2](https://github.com/DepthAnything/Depth-Anything-V2)), it directly predicts depth, resulting in superior geometric accuracy.
+
+🔗 Leveraging these available models, we developed a **nested series** (`DA3Nested-Giant-Large`). This series combines a any-view giant model with a metric model to reconstruct visual geometry at a real-world metric scale.
+
+### 🛠️ Codebase Features
+Our repository is designed to be a powerful and user-friendly toolkit for both practical application and future research.
+- 🎨 **Interactive Web UI & Gallery**: Visualize model outputs and compare results with an easy-to-use Gradio-based web interface.
+- ⚡ **Flexible Command-Line Interface (CLI)**: Powerful and scriptable CLI for batch processing and integration into custom workflows.
+- 💾 **Multiple Export Formats**: Save your results in various formats, including `glb`, `npz`, depth images, `ply`, 3DGS videos, etc, to seamlessly connect with other tools.
+- 🔧 **Extensible and Modular Design**: The codebase is structured to facilitate future research and the integration of new models or functionalities.
+
+
+
+
+
+## 🚀 Quick Start
+
+### 📦 Installation
+
+```bash
+pip install xformers torch\>=2 torchvision
+pip install -e . # Basic
+pip install --no-build-isolation git+https://github.com/nerfstudio-project/gsplat.git@0b4dddf04cb687367602c01196913cde6a743d70 # for gaussian head
+pip install -e ".[app]" # Gradio, python>=3.10
+pip install -e ".[all]" # ALL
+```
+
+For detailed model information, please refer to the [Model Cards](#-model-cards) section below.
+
+### 💻 Basic Usage
+
+```python
+import glob, os, torch
+from depth_anything_3.api import DepthAnything3
+device = torch.device("cuda")
+model = DepthAnything3.from_pretrained("depth-anything/DA3NESTED-GIANT-LARGE")
+model = model.to(device=device)
+example_path = "assets/examples/SOH"
+images = sorted(glob.glob(os.path.join(example_path, "*.png")))
+prediction = model.inference(
+ images,
+)
+# prediction.processed_images : [N, H, W, 3] uint8 array
+print(prediction.processed_images.shape)
+# prediction.depth : [N, H, W] float32 array
+print(prediction.depth.shape)
+# prediction.conf : [N, H, W] float32 array
+print(prediction.conf.shape)
+# prediction.extrinsics : [N, 3, 4] float32 array # opencv w2c or colmap format
+print(prediction.extrinsics.shape)
+# prediction.intrinsics : [N, 3, 3] float32 array
+print(prediction.intrinsics.shape)
+```
+
+```bash
+
+export MODEL_DIR=depth-anything/DA3NESTED-GIANT-LARGE
+# This can be a Hugging Face repository or a local directory
+# If you encounter network issues, consider using the following mirror: export HF_ENDPOINT=https://hf-mirror.com
+# Alternatively, you can download the model directly from Hugging Face
+export GALLERY_DIR=workspace/gallery
+mkdir -p $GALLERY_DIR
+
+# CLI auto mode with backend reuse
+da3 backend --model-dir ${MODEL_DIR} --gallery-dir ${GALLERY_DIR} # Cache model to gpu
+da3 auto assets/examples/SOH \
+ --export-format glb \
+ --export-dir ${GALLERY_DIR}/TEST_BACKEND/SOH \
+ --use-backend
+
+# CLI video processing with feature visualization
+da3 video assets/examples/robot_unitree.mp4 \
+ --fps 15 \
+ --use-backend \
+ --export-dir ${GALLERY_DIR}/TEST_BACKEND/robo \
+ --export-format glb-feat_vis \
+ --feat-vis-fps 15 \
+ --process-res-method lower_bound_resize \
+ --export-feat "11,21,31"
+
+# CLI auto mode without backend reuse
+da3 auto assets/examples/SOH \
+ --export-format glb \
+ --export-dir ${GALLERY_DIR}/TEST_CLI/SOH \
+ --model-dir ${MODEL_DIR}
+
+```
+
+The model architecture is defined in [`DepthAnything3Net`](src/depth_anything_3/model/da3.py), and specified with a Yaml config file located at [`src/depth_anything_3/configs`](src/depth_anything_3/configs). The input and output processing are handled by [`DepthAnything3`](src/depth_anything_3/api.py). To customize the model architecture, simply create a new config file (*e.g.*, `path/to/new/config`) as:
+
+```yaml
+__object__:
+ path: depth_anything_3.model.da3
+ name: DepthAnything3Net
+ args: as_params
+
+net:
+ __object__:
+ path: depth_anything_3.model.dinov2.dinov2
+ name: DinoV2
+ args: as_params
+
+ name: vitb
+ out_layers: [5, 7, 9, 11]
+ alt_start: 4
+ qknorm_start: 4
+ rope_start: 4
+ cat_token: True
+
+head:
+ __object__:
+ path: depth_anything_3.model.dualdpt
+ name: DualDPT
+ args: as_params
+
+ dim_in: &head_dim_in 1536
+ output_dim: 2
+ features: &head_features 128
+ out_channels: &head_out_channels [96, 192, 384, 768]
+```
+
+Then, the model can be created with the following code snippet.
+```python
+from depth_anything_3.cfg import create_object, load_config
+
+Model = create_object(load_config("path/to/new/config"))
+```
+
+
+
+## 📚 Useful Documentation
+
+- 🖥️ [Command Line Interface](docs/CLI.md)
+- 📑 [Python API](docs/API.md)
+- 📊 [Benchmark Evaluation](docs/BENCHMARK.md)
+
+## 🗂️ Model Cards
+
+Generally, you should observe that DA3-LARGE achieves comparable results to VGGT.
+
+The Nested series uses an Any-view model to estimate pose and depth, and a monocular metric depth estimator for scaling.
+
+⚠️ Models with the `-1.1` suffix are retrained after fixing a training bug; prefer these refreshed checkpoints. The original `DA3NESTED-GIANT-LARGE`, `DA3-GIANT`, and `DA3-LARGE` remain available but are deprecated. You could expect much better performance for street scenes with the `-1.1` models.
+
+| 🗃️ Model Name | 📏 Params | 📊 Rel. Depth | 📷 Pose Est. | 🧭 Pose Cond. | 🎨 GS | 📐 Met. Depth | ☁️ Sky Seg | 📄 License |
+|-------------------------------|-----------|---------------|--------------|---------------|-------|---------------|-----------|----------------|
+| **Nested** | | | | | | | | |
+| [DA3NESTED-GIANT-LARGE-1.1](https://huggingface.co/depth-anything/DA3NESTED-GIANT-LARGE-1.1) | 1.40B | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | CC BY-NC 4.0 |
+| [DA3NESTED-GIANT-LARGE](https://huggingface.co/depth-anything/DA3NESTED-GIANT-LARGE) | 1.40B | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | CC BY-NC 4.0 |
+| **Any-view Model** | | | | | | | | |
+| [DA3-GIANT-1.1](https://huggingface.co/depth-anything/DA3-GIANT-1.1) | 1.15B | ✅ | ✅ | ✅ | ✅ | | | CC BY-NC 4.0 |
+| [DA3-GIANT](https://huggingface.co/depth-anything/DA3-GIANT) | 1.15B | ✅ | ✅ | ✅ | ✅ | | | CC BY-NC 4.0 |
+| [DA3-LARGE-1.1](https://huggingface.co/depth-anything/DA3-LARGE-1.1) | 0.35B | ✅ | ✅ | ✅ | | | | CC BY-NC 4.0 |
+| [DA3-LARGE](https://huggingface.co/depth-anything/DA3-LARGE) | 0.35B | ✅ | ✅ | ✅ | | | | CC BY-NC 4.0 |
+| [DA3-BASE](https://huggingface.co/depth-anything/DA3-BASE) | 0.12B | ✅ | ✅ | ✅ | | | | Apache 2.0 |
+| [DA3-SMALL](https://huggingface.co/depth-anything/DA3-SMALL) | 0.08B | ✅ | ✅ | ✅ | | | | Apache 2.0 |
+| | | | | | | | | |
+| **Monocular Metric Depth** | | | | | | | | |
+| [DA3METRIC-LARGE](https://huggingface.co/depth-anything/DA3METRIC-LARGE) | 0.35B | ✅ | | | | ✅ | ✅ | Apache 2.0 |
+| | | | | | | | | |
+| **Monocular Depth** | | | | | | | | |
+| [DA3MONO-LARGE](https://huggingface.co/depth-anything/DA3MONO-LARGE) | 0.35B | ✅ | | | | | ✅ | Apache 2.0 |
+
+
+## ❓ FAQ
+
+- **Monocular Metric Depth**: To obtain metric depth in meters from `DA3METRIC-LARGE`, use `metric_depth = focal * net_output / 300.`, where `focal` is the focal length in pixels (typically the average of fx and fy from the camera intrinsic matrix K). Note that the output from `DA3NESTED-GIANT-LARGE` is already in meters.
+
+- **Ray Head (`use_ray_pose`)**: Our API and CLI support `use_ray_pose` arg, which means that the model will derive camera pose from ray head, which is generally slightly slower, but more accurate. Note that the default is `False` for faster inference speed.
+
+ AUC3 Results for DA3NESTED-GIANT-LARGE
+
+ | Model | HiRoom | ETH3D | DTU | 7Scenes | ScanNet++ |
+ |-------|------|-------|-----|---------|-----------|
+ | `ray_head` | 84.4 | 52.6 | 93.9 | 29.5 | 89.4 |
+ | `cam_head` | 80.3 | 48.4 | 94.1 | 28.5 | 85.0 |
+
+
+
+
+
+
+- **Older GPUs without XFormers support**: See [Issue #11](https://github.com/ByteDance-Seed/Depth-Anything-3/issues/11). Thanks to [@S-Mahoney](https://github.com/S-Mahoney) for the solution!
+
+
+## 🏢 Awesome DA3 Projects
+
+A community-curated list of Depth Anything 3 integrations across 3D tools, creative pipelines, robotics, and web/VR viewers, including but not limited to these. You are welcome to submit your DA3-based project via PR, and we will review and feature it if applicable.
+
+- [DA3-blender](https://github.com/xy-gao/DA3-blender): Blender addon for DA3-based 3D reconstruction from a set of images.
+
+- [ComfyUI-DepthAnythingV3](https://github.com/PozzettiAndrea/ComfyUI-DepthAnythingV3): ComfyUI nodes for Depth Anything 3, supporting single/multi-view and video-consistent depth with optional point‑cloud export.
+
+- [DA3-ROS2-Wrapper](https://github.com/GerdsenAI/GerdsenAI-Depth-Anything-3-ROS2-Wrapper): Real-time DA3 depth in ROS2 with multi-camera support.
+
+- [DA3-ROS2-CPP-TensorRT](https://github.com/ika-rwth-aachen/ros2-depth-anything-v3-trt): DA3 ROS2 C++ TensorRT Inference Node: a ROS2 node for DA3 depth estimation using TensorRT for real-time inference.
+
+- [VideoDepthViewer3D](https://github.com/amariichi/VideoDepthViewer3D): Streaming videos with DA3 metric depth to a Three.js/WebXR 3D viewer for VR/stereo playback.
+
+
+## 🧑💻 Official Codebase Core Contributors and Maintainers
+
+
+
+
+ """
+
+
+def get_description_html():
+ """
+ Generate the main description and getting started HTML.
+
+ Returns:
+ str: HTML string for the description
+ """
+ return """
+
+
+ What This Demo Does
+
+
+
+ Upload images or videos → Get MetricPoint Clouds, Cameras and Novel Views → Explore in 3D
+
+
+
+
+
+ Tip: Landscape-oriented images or videos are preferred for best 3D recovering.
+
+
+
+
+
+ """
+
+
+def get_acknowledgements_html():
+ """
+ Generate the acknowledgements section HTML.
+
+ Returns:
+ str: HTML string for the acknowledgements
+ """
+ return """
+
"
+ ""
+ if _gallery_dir and os.path.exists(_gallery_dir)
+ else ""
+ )
+ + """
+
+
+
+
+
+ """
+ )
+ return HTMLResponse(html_content)
+
+ @_app.get("/dashboard", response_class=HTMLResponse)
+ async def dashboard():
+ """HTML dashboard for monitoring backend status and tasks."""
+ if _backend is None:
+ return HTMLResponse("
Backend not initialized
", status_code=500)
+
+ # Get backend status
+ status = _backend.get_status()
+
+ # Safely format status values
+ if status["load_time"] is not None:
+ load_time_str = f"{status['load_time']:.2f}s"
+ else:
+ load_time_str = "Not loaded"
+
+ if status["uptime"] is not None:
+ uptime_str = f"{status['uptime']:.2f}s"
+ else:
+ uptime_str = "Not running"
+
+ # Get tasks information
+ active_tasks = [task for task in _tasks.values() if task.status in ["pending", "running"]]
+ completed_tasks = [
+ task for task in _tasks.values() if task.status in ["completed", "failed"]
+ ]
+
+ # Generate task HTML
+ if active_tasks:
+ active_tasks_html = "".join(
+ _render_task_card(task, "running") for task in active_tasks
+ )
+ else:
+ active_tasks_html = "
No active tasks
"
+
+ if completed_tasks:
+ completed_tasks_html = "".join(
+ _render_task_card(task, "completed") for task in completed_tasks[-10:]
+ )
+ else:
+ completed_tasks_html = "
Last updated: {time.strftime('%Y-%m-%d %H:%M:%S')}
+
+ {active_tasks_html}
+
+
+
+
Recent Completed Tasks
+ {completed_tasks_html}
+
+
+
+
+
+
+ """
+
+ return HTMLResponse(html_content)
+
+ @_app.get("/status")
+ async def get_status():
+ """Get backend status with GPU memory information."""
+ if _backend is None:
+ raise HTTPException(status_code=500, detail="Backend not initialized")
+
+ status = _backend.get_status()
+
+ # Add GPU memory information
+ gpu_memory = get_gpu_memory_info()
+ if gpu_memory:
+ status["gpu_memory"] = {
+ "total_gb": round(gpu_memory["total_gb"], 2),
+ "allocated_gb": round(gpu_memory["allocated_gb"], 2),
+ "reserved_gb": round(gpu_memory["reserved_gb"], 2),
+ "free_gb": round(gpu_memory["free_gb"], 2),
+ "utilization_percent": round(gpu_memory["utilization"], 1),
+ }
+ else:
+ status["gpu_memory"] = None
+
+ return status
+
+ @_app.post(
+ "/inference", response_model=InferenceResponse, dependencies=[Depends(_require_api_key)]
+ )
+ async def run_inference(request: InferenceRequest):
+ """Submit inference task and return task ID."""
+ global _running_task_id
+
+ if _backend is None:
+ raise HTTPException(status_code=500, detail="Backend not initialized")
+
+ if request.export_dir:
+ try:
+ validate_export_dir(request.export_dir, _gallery_dir)
+ except ValueError as e:
+ raise HTTPException(status_code=400, detail=str(e))
+
+ # Generate unique task ID
+ task_id = str(uuid.uuid4())
+
+ # Create task status
+ if _running_task_id is not None:
+ status_msg = f"[{task_id}] Task queued (waiting for {_running_task_id} to complete)"
+ else:
+ status_msg = f"[{task_id}] Task submitted"
+
+ _tasks[task_id] = TaskStatus(
+ task_id=task_id,
+ status="pending",
+ message=status_msg,
+ created_at=time.time(),
+ export_dir=request.export_dir,
+ request=request,
+ # Record essential parameters
+ num_images=len(request.image_paths),
+ export_format=request.export_format,
+ process_res_method=request.process_res_method,
+ video_path=(
+ request.image_paths[0] if request.image_paths else None
+ ), # Use first image path as video reference
+ )
+
+ # Add task to queue
+ _task_queue.append(task_id)
+
+ # If no task is running, start processing the queue
+ if _running_task_id is None:
+ _process_next_task()
+
+ return InferenceResponse(
+ success=True,
+ message="Task submitted successfully",
+ task_id=task_id,
+ export_dir=request.export_dir,
+ export_format=request.export_format,
+ )
+
+ @_app.get("/task/{task_id}", response_model=TaskStatus)
+ async def get_task_status(task_id: str):
+ """Get task status by task ID."""
+ if task_id not in _tasks:
+ raise HTTPException(status_code=404, detail="Task not found")
+
+ return _tasks[task_id]
+
+ @_app.get("/gpu-memory")
+ async def get_gpu_memory():
+ """Get detailed GPU memory information."""
+ gpu_memory = get_gpu_memory_info()
+ if gpu_memory is None:
+ return {
+ "available": False,
+ "message": "CUDA not available or memory info cannot be retrieved",
+ }
+
+ return {
+ "available": True,
+ "total_gb": round(gpu_memory["total_gb"], 2),
+ "allocated_gb": round(gpu_memory["allocated_gb"], 2),
+ "reserved_gb": round(gpu_memory["reserved_gb"], 2),
+ "free_gb": round(gpu_memory["free_gb"], 2),
+ "utilization_percent": round(gpu_memory["utilization"], 1),
+ "status": (
+ "healthy"
+ if gpu_memory["utilization"] < 80
+ else "warning" if gpu_memory["utilization"] < 95 else "critical"
+ ),
+ }
+
+ @_app.get("/tasks")
+ async def list_tasks():
+ """List all tasks."""
+ # Separate active and completed tasks
+ active_tasks = [task for task in _tasks.values() if task.status in ["pending", "running"]]
+ completed_tasks = [
+ task for task in _tasks.values() if task.status in ["completed", "failed"]
+ ]
+
+ return {
+ "tasks": list(_tasks.values()),
+ "active_tasks": active_tasks,
+ "completed_tasks": completed_tasks,
+ "active_count": len(active_tasks),
+ "total_count": len(_tasks),
+ }
+
+ @_app.post("/cleanup")
+ async def manual_cleanup():
+ """Manually trigger task cleanup."""
+ try:
+ _cleanup_old_tasks()
+ return {"message": "Cleanup completed", "active_tasks": len(_tasks)}
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Cleanup failed: {str(e)}")
+
+ @_app.delete("/task/{task_id}")
+ async def delete_task(task_id: str):
+ """Delete a specific task."""
+ if task_id not in _tasks:
+ raise HTTPException(status_code=404, detail="Task not found")
+
+ # Only allow deletion of completed/failed tasks
+ if _tasks[task_id].status not in ["completed", "failed"]:
+ raise HTTPException(status_code=400, detail="Cannot delete running or pending tasks")
+
+ del _tasks[task_id]
+ return {"message": f"Task {task_id} deleted successfully"}
+
+ @_app.post("/reload")
+ async def reload_model():
+ """Reload the model."""
+ if _backend is None:
+ raise HTTPException(status_code=500, detail="Backend not initialized")
+
+ try:
+ _backend.model = None
+ _backend.model_loaded = False
+ _backend.load_model()
+ return {"message": "Model reloaded successfully"}
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Failed to reload model: {str(e)}")
+
+ # ============================================================================
+ # Gallery routes
+ # ============================================================================
+
+ if _gallery_dir and os.path.exists(_gallery_dir):
+ # Load gallery HTML page (with modified paths for /gallery/ subdirectory)
+ _gallery_html = _load_gallery_html()
+
+ @_app.get("/gallery/", response_class=HTMLResponse)
+ @_app.get("/gallery", response_class=HTMLResponse)
+ async def gallery_home():
+ """Gallery home page."""
+ return HTMLResponse(_gallery_html)
+
+ @_app.get("/gallery/manifest.json")
+ async def gallery_manifest():
+ """Get gallery group list."""
+ try:
+ return build_group_list(_gallery_dir)
+ except Exception as e:
+ raise HTTPException(
+ status_code=500, detail=f"Failed to build group list: {str(e)}"
+ )
+
+ @_app.get("/gallery/manifest/{group}.json")
+ async def gallery_group_manifest(group: str):
+ """Get manifest for a specific group."""
+ if not _is_plain_name(group):
+ raise HTTPException(status_code=400, detail="Invalid group name")
+ try:
+ return build_group_manifest(_gallery_dir, group)
+ except Exception as e:
+ raise HTTPException(
+ status_code=500, detail=f"Failed to build group manifest: {str(e)}"
+ )
+
+ @_app.get("/gallery/{path:path}")
+ async def gallery_files(path: str):
+ """Serve gallery static files (GLB, JPG, etc.)."""
+ # Security check: prevent directory traversal
+ path_parts = path.split("/")
+ if any(not _is_plain_name(part) for part in path_parts if part):
+ raise HTTPException(status_code=400, detail="Invalid path")
+
+ file_path = os.path.join(_gallery_dir, *path_parts)
+
+ # Ensure the file is within gallery directory
+ real_file_path = os.path.realpath(file_path)
+ real_gallery_dir = os.path.realpath(_gallery_dir)
+ if not real_file_path.startswith(real_gallery_dir):
+ raise HTTPException(status_code=403, detail="Access denied")
+
+ if not os.path.exists(file_path) or not os.path.isfile(file_path):
+ raise HTTPException(status_code=404, detail="File not found")
+
+ return FileResponse(file_path)
+
+ return _app
+
+
+def start_server(
+ model_dir: str,
+ device: str = "cuda",
+ host: str = "127.0.0.1",
+ port: int = 8000,
+ gallery_dir: Optional[str] = None,
+ api_key: Optional[str] = None,
+ allow_unauthenticated: bool = False,
+):
+ """Start the backend server."""
+ api_key = api_key or os.environ.get("DA3_BACKEND_API_KEY")
+ auto_generated_key = False
+
+ if host not in _LOOPBACK_HOSTS and not api_key and not allow_unauthenticated:
+ # No key was configured for a host reachable beyond this machine: generate one
+ # for this run rather than requiring the operator to come up with one first.
+ api_key = secrets.token_urlsafe(32)
+ auto_generated_key = True
+
+ app = create_app(model_dir, device, gallery_dir, api_key=api_key)
+
+ print("Starting Depth Anything 3 Backend...")
+ print(f"Model directory: {model_dir}")
+ print(f"Device: {device}")
+ print(f"Server: http://{host}:{port}")
+ print(f"Dashboard: http://{host}:{port}/dashboard")
+ print(f"API Status: http://{host}:{port}/status")
+
+ if gallery_dir and os.path.exists(gallery_dir):
+ print(f"Gallery: http://{host}:{port}/gallery/")
+
+ if auto_generated_key:
+ print("=" * 60)
+ print("No --api-key was set, so one was generated for this run:")
+ print(f" X-API-Key: {api_key}")
+ print("Set DA3_BACKEND_API_KEY to this value wherever you submit jobs from")
+ print("(e.g. `da3 ... --use-backend`), or pass --api-key / set")
+ print("DA3_BACKEND_API_KEY before starting the backend for a key that stays")
+ print("the same across restarts. Use --allow-unauthenticated to skip this.")
+ elif api_key:
+ print("Authentication: X-API-Key header required for /inference")
+
+ print("=" * 60)
+ print("Backend is running! You can now:")
+ print(f" • Open home page: http://{host}:{port}")
+ print(f" • Open dashboard: http://{host}:{port}/dashboard")
+ print(f" • Check API status: http://{host}:{port}/status")
+
+ if gallery_dir and os.path.exists(gallery_dir):
+ print(f" • Browse gallery: http://{host}:{port}/gallery/")
+
+ print(" • Submit inference tasks via API")
+ print("=" * 60)
+
+ uvicorn.run(app, host=host, port=port, log_level="info")
+
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description="Depth Anything 3 Backend Server")
+ parser.add_argument("--model-dir", required=True, help="Model directory path")
+ parser.add_argument("--device", default="cuda", help="Device to use")
+ parser.add_argument("--host", default="127.0.0.1", help="Host to bind to")
+ parser.add_argument("--port", type=int, default=8000, help="Port to bind to")
+ parser.add_argument("--gallery-dir", help="Gallery directory path (optional)")
+ parser.add_argument(
+ "--api-key",
+ default=None,
+ help="Require this API key (X-API-Key header) on inference requests. Falls back "
+ "to the DA3_BACKEND_API_KEY env var, or an auto-generated key when binding to a "
+ "non-loopback host.",
+ )
+ parser.add_argument(
+ "--allow-unauthenticated",
+ action="store_true",
+ help="Skip authentication entirely on a non-loopback host, instead of using an "
+ "auto-generated API key.",
+ )
+
+ args = parser.parse_args()
+ start_server(
+ args.model_dir,
+ args.device,
+ args.host,
+ args.port,
+ args.gallery_dir,
+ api_key=args.api_key,
+ allow_unauthenticated=args.allow_unauthenticated,
+ )
diff --git a/cameras/source/src/depth_anything_3/services/gallery.py b/cameras/source/src/depth_anything_3/services/gallery.py
new file mode 100644
index 0000000000000000000000000000000000000000..f72bb5e5f6defbc24cf2278a53b7162a8ad5519d
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/services/gallery.py
@@ -0,0 +1,806 @@
+#!/usr/bin/env python3
+# flake8: noqa: E501
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Depth Anything 3 Gallery Server (two-level, single-file)
+Now supports paginated depth preview (4 per page).
+"""
+
+import argparse
+import json
+import mimetypes
+import os
+import posixpath
+import sys
+from functools import partial
+from http import HTTPStatus
+from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
+from urllib.parse import quote, unquote
+
+# ------------------------------ Embedded HTML ------------------------------ #
+
+HTML_PAGE = r"""
+
+
+
+ Depth Anything 3 Gallery
+
+
+
+
+
+
+
+
+
+
+
Depth Anything 3 Gallery
+
+
+
+
Level 1 shows groups only; click a group to browse scenes and previews.
+
+
+
+
+
+
+ 🎯 Depth Anything 3 Gallery
+
+
+ Explore 3D reconstructions and depth visualizations from Depth Anything 3.
+ Browse through groups of scenes, preview 3D models, and examine depth maps interactively.
+
+
+
+
+
+
No available groups
+
+
+
+
+
+
No available scenes in this group
+
+
+
+
+
+
+
Loading…
+
+
+
+
⬇
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+# ------------------------------ Utilities ------------------------------ #
+
+IMAGE_EXTS = (".png", ".jpg", ".jpeg", ".webp", ".bmp")
+
+
+def _url_join(*parts: str) -> str:
+ norm = posixpath.join(*[p.replace("\\", "/") for p in parts])
+ segs = [s for s in norm.split("/") if s not in ("", ".")]
+ return "/".join(quote(s) for s in segs)
+
+
+def _is_plain_name(name: str) -> bool:
+ return all(c not in name for c in ("/", "\\")) and name not in (".", "..")
+
+
+def build_group_list(root_dir: str) -> dict:
+ groups = []
+ try:
+ for gname in sorted(os.listdir(root_dir)):
+ gpath = os.path.join(root_dir, gname)
+ if not os.path.isdir(gpath):
+ continue
+ has_scene = False
+ try:
+ for sname in os.listdir(gpath):
+ spath = os.path.join(gpath, sname)
+ if not os.path.isdir(spath):
+ continue
+ if os.path.exists(os.path.join(spath, "scene.glb")) and os.path.exists(
+ os.path.join(spath, "scene.jpg")
+ ):
+ has_scene = True
+ break
+ except Exception:
+ pass
+ if has_scene:
+ groups.append({"id": gname, "title": gname})
+ except Exception as e:
+ print(f"[warn] build_group_list failed: {e}", file=sys.stderr)
+ return {"groups": groups}
+
+
+def build_group_manifest(root_dir: str, group: str) -> dict:
+ items = []
+ gpath = os.path.join(root_dir, group)
+ try:
+ if not os.path.isdir(gpath):
+ return {"group": group, "items": []}
+ for sname in sorted(os.listdir(gpath)):
+ spath = os.path.join(gpath, sname)
+ if not os.path.isdir(spath):
+ continue
+ glb_fs = os.path.join(spath, "scene.glb")
+ jpg_fs = os.path.join(spath, "scene.jpg")
+ if not (os.path.exists(glb_fs) and os.path.exists(jpg_fs)):
+ continue
+ depth_images = []
+ dpath = os.path.join(spath, "depth_vis")
+ if os.path.isdir(dpath):
+ files = [
+ f for f in os.listdir(dpath) if os.path.splitext(f)[1].lower() in IMAGE_EXTS
+ ]
+ for fn in sorted(files):
+ depth_images.append("/" + _url_join(group, sname, "depth_vis", fn))
+ items.append(
+ {
+ "id": sname,
+ "title": sname,
+ "model": "/" + _url_join(group, sname, "scene.glb"),
+ "thumbnail": "/" + _url_join(group, sname, "scene.jpg"),
+ "depth_images": depth_images,
+ }
+ )
+ except Exception as e:
+ print(f"[warn] build_group_manifest failed for {group}: {e}", file=sys.stderr)
+ return {"group": group, "items": items}
+
+
+class GalleryHandler(SimpleHTTPRequestHandler):
+ def __init__(self, *args, directory=None, **kwargs):
+ super().__init__(*args, directory=directory, **kwargs)
+
+ def do_GET(self):
+ if self.path in ("/", "/index.html") or self.path.startswith("/?"):
+ content = HTML_PAGE.encode("utf-8")
+ self.send_response(HTTPStatus.OK)
+ self.send_header("Content-Type", "text/html; charset=utf-8")
+ self.send_header("Content-Length", str(len(content)))
+ self.send_header("Cache-Control", "no-store")
+ self.end_headers()
+ self.wfile.write(content)
+ return
+ if self.path == "/manifest.json":
+ data = json.dumps(
+ build_group_list(self.directory), ensure_ascii=False, indent=2
+ ).encode("utf-8")
+ self.send_response(HTTPStatus.OK)
+ self.send_header("Content-Type", "application/json; charset=utf-8")
+ self.send_header("Content-Length", str(len(data)))
+ self.send_header("Cache-Control", "no-store")
+ self.end_headers()
+ self.wfile.write(data)
+ return
+ if self.path.startswith("/manifest/") and self.path.endswith(".json"):
+ group_enc = self.path[len("/manifest/") : -len(".json")]
+ try:
+ group = unquote(group_enc)
+ except Exception:
+ group = group_enc
+ if not _is_plain_name(group):
+ self.send_error(HTTPStatus.BAD_REQUEST, "Invalid group name")
+ return
+ data = json.dumps(
+ build_group_manifest(self.directory, group), ensure_ascii=False, indent=2
+ ).encode("utf-8")
+ self.send_response(HTTPStatus.OK)
+ self.send_header("Content-Type", "application/json; charset=utf-8")
+ self.send_header("Content-Length", str(len(data)))
+ self.send_header("Cache-Control", "no-store")
+ self.end_headers()
+ self.wfile.write(data)
+ return
+ if self.path == "/favicon.ico":
+ self.send_response(HTTPStatus.NO_CONTENT)
+ self.end_headers()
+ return
+ return super().do_GET()
+
+ def list_directory(self, path):
+ self.send_error(HTTPStatus.NOT_FOUND, "Directory listing disabled")
+ return None
+
+
+def gallery():
+ parser = argparse.ArgumentParser(
+ description="Depth Anything 3 Gallery Server (two-level, with pagination)"
+ )
+ parser.add_argument(
+ "-d", "--dir", required=True, help="Gallery root directory (two-level: group/scene)"
+ )
+ parser.add_argument("-p", "--port", type=int, default=8000, help="Port (default 8000)")
+ parser.add_argument("--host", default="127.0.0.1", help="Host address (default 127.0.0.1)")
+ parser.add_argument("--open", action="store_true", help="Open browser after launch")
+ args = parser.parse_args()
+
+ root_dir = os.path.abspath(args.dir)
+ if not os.path.isdir(root_dir):
+ print(f"[error] Directory not found: {root_dir}", file=sys.stderr)
+ sys.exit(1)
+
+ Handler = partial(GalleryHandler, directory=root_dir)
+ server = ThreadingHTTPServer((args.host, args.port), Handler)
+
+ addr = f"http://{args.host}:{args.port}/"
+ print(f"[info] Serving gallery from: {root_dir}")
+ print(f"[info] Open: {addr}")
+
+ if args.open:
+ try:
+ import webbrowser
+
+ webbrowser.open(addr)
+ except Exception as e:
+ print(f"[warn] Failed to open browser: {e}", file=sys.stderr)
+
+ try:
+ server.serve_forever()
+ except KeyboardInterrupt:
+ print("\n[info] Shutting down...")
+ finally:
+ server.server_close()
+
+
+def main():
+ """Main entry point for gallery server."""
+ mimetypes.add_type("model/gltf-binary", ".glb")
+ gallery()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/cameras/source/src/depth_anything_3/services/inference_service.py b/cameras/source/src/depth_anything_3/services/inference_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f3ad512360179215739e26532b9269bf9580d11
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/services/inference_service.py
@@ -0,0 +1,247 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Unified Inference Service
+Provides unified interface for local and remote inference
+"""
+
+import os
+
+from typing import Any, Dict, List, Optional, Union
+import numpy as np
+import requests
+import typer
+
+from ..api import DepthAnything3
+
+
+class InferenceService:
+ """Unified inference service class"""
+
+ def __init__(self, model_dir: str, device: str = "cuda"):
+ self.model_dir = model_dir
+ self.device = device
+ self.model = None
+
+ def load_model(self):
+ """Load model"""
+ if self.model is None:
+ typer.echo(f"Loading model from {self.model_dir}...")
+ self.model = DepthAnything3.from_pretrained(self.model_dir).to(self.device)
+ return self.model
+
+ def run_local_inference(
+ self,
+ image_paths: List[str],
+ export_dir: str,
+ export_format: str = "mini_npz-glb",
+ process_res: int = 504,
+ process_res_method: str = "upper_bound_resize",
+ export_feat_layers: List[int] = None,
+ extrinsics: Optional[np.ndarray] = None,
+ intrinsics: Optional[np.ndarray] = None,
+ align_to_input_ext_scale: bool = True,
+ use_ray_pose: bool = False,
+ ref_view_strategy: str = "saddle_balanced",
+ conf_thresh_percentile: float = 40.0,
+ num_max_points: int = 1_000_000,
+ show_cameras: bool = True,
+ feat_vis_fps: int = 15,
+ ) -> Any:
+ """Run local inference"""
+ if export_feat_layers is None:
+ export_feat_layers = []
+
+ model = self.load_model()
+
+ # Prepare inference parameters
+ inference_kwargs = {
+ "image": image_paths,
+ "export_dir": export_dir,
+ "export_format": export_format,
+ "process_res": process_res,
+ "process_res_method": process_res_method,
+ "export_feat_layers": export_feat_layers,
+ "align_to_input_ext_scale": align_to_input_ext_scale,
+ "use_ray_pose": use_ray_pose,
+ "ref_view_strategy": ref_view_strategy,
+ "conf_thresh_percentile": conf_thresh_percentile,
+ "num_max_points": num_max_points,
+ "show_cameras": show_cameras,
+ "feat_vis_fps": feat_vis_fps,
+ }
+
+ # Add pose data (if exists)
+ if extrinsics is not None:
+ inference_kwargs["extrinsics"] = extrinsics
+ if intrinsics is not None:
+ inference_kwargs["intrinsics"] = intrinsics
+
+ # Run inference
+ typer.echo(f"Running inference on {len(image_paths)} images...")
+ prediction = model.inference(**inference_kwargs)
+
+ typer.echo(f"Results saved to {export_dir}")
+ typer.echo(f"Export format: {export_format}")
+
+ return prediction
+
+ def run_backend_inference(
+ self,
+ image_paths: List[str],
+ export_dir: str,
+ backend_url: str,
+ export_format: str = "mini_npz-glb",
+ process_res: int = 504,
+ process_res_method: str = "upper_bound_resize",
+ export_feat_layers: List[int] = None,
+ extrinsics: Optional[np.ndarray] = None,
+ intrinsics: Optional[np.ndarray] = None,
+ align_to_input_ext_scale: bool = True,
+ use_ray_pose: bool = False,
+ ref_view_strategy: str = "saddle_balanced",
+ conf_thresh_percentile: float = 40.0,
+ num_max_points: int = 1_000_000,
+ show_cameras: bool = True,
+ feat_vis_fps: int = 15,
+ ) -> Dict[str, Any]:
+ """Run backend inference"""
+ if export_feat_layers is None:
+ export_feat_layers = []
+
+ # Check backend status
+ if not self._check_backend_status(backend_url):
+ raise typer.BadParameter(f"Backend service is not running at {backend_url}")
+
+ # Prepare payload
+ payload = {
+ "image_paths": image_paths,
+ "export_dir": export_dir,
+ "export_format": export_format,
+ "process_res": process_res,
+ "process_res_method": process_res_method,
+ "export_feat_layers": export_feat_layers,
+ "align_to_input_ext_scale": align_to_input_ext_scale,
+ "use_ray_pose": use_ray_pose,
+ "ref_view_strategy": ref_view_strategy,
+ "conf_thresh_percentile": conf_thresh_percentile,
+ "num_max_points": num_max_points,
+ "show_cameras": show_cameras,
+ "feat_vis_fps": feat_vis_fps,
+ }
+
+ # Add pose data (if exists)
+ if extrinsics is not None:
+ payload["extrinsics"] = [ext.astype(np.float64).tolist() for ext in extrinsics]
+ if intrinsics is not None:
+ payload["intrinsics"] = [intr.astype(np.float64).tolist() for intr in intrinsics]
+
+ # Submit task
+ typer.echo("Submitting inference task to backend...")
+ # Sent automatically when the backend was started with a matching --api-key /
+ # DA3_BACKEND_API_KEY, so client and server share the same env-var convention.
+ api_key = os.environ.get("DA3_BACKEND_API_KEY")
+ headers = {"X-API-Key": api_key} if api_key else None
+ try:
+ response = requests.post(
+ f"{backend_url}/inference", json=payload, headers=headers, timeout=30
+ )
+ response.raise_for_status()
+ result = response.json()
+
+ if result["success"]:
+ task_id = result["task_id"]
+ typer.echo("Task submitted successfully!")
+ typer.echo(f"Task ID: {task_id}")
+ typer.echo(f"Results will be saved to: {export_dir}")
+ typer.echo(f"Check backend logs for progress updates with task ID: {task_id}")
+ return result
+ else:
+ raise typer.BadParameter(
+ f"Backend inference submission failed: {result['message']}"
+ )
+ except requests.exceptions.RequestException as e:
+ raise typer.BadParameter(f"Backend inference submission failed: {e}")
+
+ def _check_backend_status(self, backend_url: str) -> bool:
+ """Check backend status"""
+ try:
+ response = requests.get(f"{backend_url}/status", timeout=5)
+ return response.status_code == 200
+ except Exception:
+ return False
+
+
+def run_inference(
+ image_paths: List[str],
+ export_dir: str,
+ model_dir: str,
+ device: str = "cuda",
+ backend_url: Optional[str] = None,
+ export_format: str = "mini_npz-glb",
+ process_res: int = 504,
+ process_res_method: str = "upper_bound_resize",
+ export_feat_layers: List[int] = None,
+ extrinsics: Optional[np.ndarray] = None,
+ intrinsics: Optional[np.ndarray] = None,
+ align_to_input_ext_scale: bool = True,
+ use_ray_pose: bool = False,
+ ref_view_strategy: str = "saddle_balanced",
+ conf_thresh_percentile: float = 40.0,
+ num_max_points: int = 1_000_000,
+ show_cameras: bool = True,
+ feat_vis_fps: int = 15,
+) -> Union[Any, Dict[str, Any]]:
+ """Unified inference interface"""
+
+ service = InferenceService(model_dir, device)
+
+ if backend_url:
+ return service.run_backend_inference(
+ image_paths=image_paths,
+ export_dir=export_dir,
+ backend_url=backend_url,
+ export_format=export_format,
+ process_res=process_res,
+ process_res_method=process_res_method,
+ export_feat_layers=export_feat_layers,
+ extrinsics=extrinsics,
+ intrinsics=intrinsics,
+ align_to_input_ext_scale=align_to_input_ext_scale,
+ use_ray_pose=use_ray_pose,
+ ref_view_strategy=ref_view_strategy,
+ conf_thresh_percentile=conf_thresh_percentile,
+ num_max_points=num_max_points,
+ show_cameras=show_cameras,
+ feat_vis_fps=feat_vis_fps,
+ )
+ else:
+ return service.run_local_inference(
+ image_paths=image_paths,
+ export_dir=export_dir,
+ export_format=export_format,
+ process_res=process_res,
+ process_res_method=process_res_method,
+ export_feat_layers=export_feat_layers,
+ extrinsics=extrinsics,
+ intrinsics=intrinsics,
+ align_to_input_ext_scale=align_to_input_ext_scale,
+ use_ray_pose=use_ray_pose,
+ ref_view_strategy=ref_view_strategy,
+ conf_thresh_percentile=conf_thresh_percentile,
+ num_max_points=num_max_points,
+ show_cameras=show_cameras,
+ feat_vis_fps=feat_vis_fps,
+ )
diff --git a/cameras/source/src/depth_anything_3/services/input_handlers.py b/cameras/source/src/depth_anything_3/services/input_handlers.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc0536b51e5c71bb6fb9d10f8e74fbb12fc42d31
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/services/input_handlers.py
@@ -0,0 +1,266 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Input Processing Service
+Handles different types of inputs (image, images, colmap, video)
+"""
+
+import glob
+import os
+from typing import List, Tuple
+import cv2
+import numpy as np
+import typer
+
+from ..utils.read_write_model import read_model
+
+
+class InputHandler:
+ """Base input handler class"""
+
+ @staticmethod
+ def validate_path(path: str, path_type: str = "file") -> str:
+ """Validate path"""
+ if not os.path.exists(path):
+ raise typer.BadParameter(f"{path_type} not found: {path}")
+ return path
+
+ @staticmethod
+ def handle_export_dir(export_dir: str, auto_cleanup: bool = False) -> str:
+ """Handle export directory"""
+ if os.path.exists(export_dir):
+ if auto_cleanup:
+ typer.echo(f"Auto-cleaning existing export directory: {export_dir}")
+ import shutil
+
+ shutil.rmtree(export_dir)
+ os.makedirs(export_dir, exist_ok=True)
+ else:
+ typer.echo(f"Export directory '{export_dir}' already exists.")
+ if typer.confirm("Do you want to clean it and continue?"):
+ import shutil
+
+ shutil.rmtree(export_dir)
+ os.makedirs(export_dir, exist_ok=True)
+ typer.echo(f"Cleaned export directory: {export_dir}")
+ else:
+ typer.echo("Operation cancelled.")
+ raise typer.Exit(0)
+ else:
+ os.makedirs(export_dir, exist_ok=True)
+ return export_dir
+
+
+class ImageHandler(InputHandler):
+ """Single image handler"""
+
+ @staticmethod
+ def process(image_path: str) -> List[str]:
+ """Process single image"""
+ InputHandler.validate_path(image_path, "Image file")
+ return [image_path]
+
+
+class ImagesHandler(InputHandler):
+ """Image directory handler"""
+
+ @staticmethod
+ def process(images_dir: str, image_extensions: str = "png,jpg,jpeg") -> List[str]:
+ """Process image directory"""
+ InputHandler.validate_path(images_dir, "Images directory")
+
+ # Parse extensions
+ extensions = [ext.strip().lower() for ext in image_extensions.split(",")]
+ extensions = [ext if ext.startswith(".") else f".{ext}" for ext in extensions]
+
+ # Find image files
+ image_files = []
+ for ext in extensions:
+ pattern = f"*{ext}"
+ image_files.extend(glob.glob(os.path.join(images_dir, pattern)))
+ image_files.extend(glob.glob(os.path.join(images_dir, pattern.upper())))
+
+ image_files = sorted(list(set(image_files))) # Remove duplicates and sort
+
+ if not image_files:
+ raise typer.BadParameter(
+ f"No image files found in {images_dir} with extensions: {extensions}"
+ )
+
+ typer.echo(f"Found {len(image_files)} images to process")
+ return image_files
+
+
+class ColmapHandler(InputHandler):
+ """COLMAP data handler"""
+
+ @staticmethod
+ def process(
+ colmap_dir: str, sparse_subdir: str = ""
+ ) -> Tuple[List[str], np.ndarray, np.ndarray]:
+ """Process COLMAP data"""
+ InputHandler.validate_path(colmap_dir, "COLMAP directory")
+
+ # Build paths
+ images_dir = os.path.join(colmap_dir, "images")
+ if sparse_subdir:
+ sparse_dir = os.path.join(colmap_dir, "sparse", sparse_subdir)
+ else:
+ sparse_dir = os.path.join(colmap_dir, "sparse")
+
+ InputHandler.validate_path(images_dir, "Images directory")
+ InputHandler.validate_path(sparse_dir, "Sparse reconstruction directory")
+
+ # Load COLMAP data
+ typer.echo("Loading COLMAP reconstruction data...")
+ try:
+ cameras, images, points3D = read_model(sparse_dir)
+
+ typer.echo(
+ f"Loaded COLMAP data: {len(cameras)} cameras, {len(images)} images, "
+ f"{len(points3D)} 3D points."
+ )
+
+ # Get image files and pose data
+ image_files = []
+ extrinsics = []
+ intrinsics = []
+
+ for image_id, image_data in images.items():
+ image_name = image_data.name
+ image_path = os.path.join(images_dir, image_name)
+
+ if os.path.exists(image_path):
+ image_files.append(image_path)
+
+ # Get camera parameters
+ camera = cameras[image_data.camera_id]
+
+ # Convert quaternion to rotation matrix
+ R = image_data.qvec2rotmat()
+ t = image_data.tvec
+
+ # Create extrinsic matrix (world to camera)
+ extrinsic = np.eye(4)
+ extrinsic[:3, :3] = R
+ extrinsic[:3, 3] = t
+ extrinsics.append(extrinsic)
+
+ # Create intrinsic matrix
+ if camera.model == "PINHOLE":
+ fx, fy, cx, cy = camera.params
+ elif camera.model == "SIMPLE_PINHOLE":
+ f, cx, cy = camera.params
+ fx = fy = f
+ else:
+ # For other models, use basic pinhole approximation
+ fx = fy = camera.params[0] if len(camera.params) > 0 else 1000
+ cx = camera.width / 2
+ cy = camera.height / 2
+
+ intrinsic = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]])
+ intrinsics.append(intrinsic)
+
+ if not image_files:
+ raise typer.BadParameter("No valid images found in COLMAP data")
+
+ typer.echo(f"Found {len(image_files)} valid images with pose data")
+
+ return image_files, np.array(extrinsics), np.array(intrinsics)
+
+ except Exception as e:
+ raise typer.BadParameter(f"Failed to load COLMAP data: {e}")
+
+
+class VideoHandler(InputHandler):
+ """Video handler"""
+
+ @staticmethod
+ def process(video_path: str, output_dir: str, fps: float = 1.0) -> List[str]:
+ """Process video, extract frames"""
+ InputHandler.validate_path(video_path, "Video file")
+
+ cap = cv2.VideoCapture(video_path)
+ if not cap.isOpened():
+ raise typer.BadParameter(f"Cannot open video: {video_path}")
+
+ # Get video properties
+ video_fps = cap.get(cv2.CAP_PROP_FPS)
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
+ duration = total_frames / video_fps
+
+ # Calculate frame interval (ensure at least 1)
+ frame_interval = max(1, int(video_fps / fps))
+ actual_fps = video_fps / frame_interval
+
+ typer.echo(f"Video FPS: {video_fps:.2f}, Duration: {duration:.2f}s")
+
+ # Warn if requested FPS is higher than video FPS
+ if fps > video_fps:
+ typer.echo(
+ f"⚠️ Warning: Requested sampling FPS ({fps:.2f}) exceeds video FPS ({video_fps:.2f})", # noqa: E501
+ err=True,
+ )
+ typer.echo(
+ f"⚠️ Using maximum available FPS: {actual_fps:.2f} (extracting every frame)",
+ err=True,
+ )
+
+ typer.echo(f"Extracting frames at {actual_fps:.2f} FPS (every {frame_interval} frame(s))")
+
+ # Create output directory
+ frames_dir = os.path.join(output_dir, "input_images")
+ os.makedirs(frames_dir, exist_ok=True)
+
+ frame_count = 0
+ saved_count = 0
+
+ while True:
+ ret, frame = cap.read()
+ if not ret:
+ break
+
+ if frame_count % frame_interval == 0:
+ frame_path = os.path.join(frames_dir, f"{saved_count:06d}.png")
+ cv2.imwrite(frame_path, frame)
+ saved_count += 1
+
+ frame_count += 1
+
+ cap.release()
+ typer.echo(f"Extracted {saved_count} frames to {frames_dir}")
+
+ # Get frame file list
+ frame_files = sorted(
+ [f for f in os.listdir(frames_dir) if f.endswith((".png", ".jpg", ".jpeg"))]
+ )
+ if not frame_files:
+ raise typer.BadParameter("No frames extracted from video")
+
+ return [os.path.join(frames_dir, f) for f in frame_files]
+
+
+def parse_export_feat(export_feat_str: str) -> List[int]:
+ """Parse export_feat parameter"""
+ if not export_feat_str:
+ return []
+
+ try:
+ return [int(x.strip()) for x in export_feat_str.split(",") if x.strip()]
+ except ValueError:
+ raise typer.BadParameter(
+ f"Invalid export_feat format: {export_feat_str}. "
+ "Use comma-separated integers like '0,1,2'"
+ )
diff --git a/cameras/source/src/depth_anything_3/specs.py b/cameras/source/src/depth_anything_3/specs.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe5b30255e9fd48d988ff00c896fb3dbadf197ea
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/specs.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import Any, Optional
+import numpy as np
+import torch
+
+
+@dataclass
+class Gaussians:
+ """3DGS parameters, all in world space"""
+
+ means: torch.Tensor # world points, "batch gaussian dim"
+ scales: torch.Tensor # scales_std, "batch gaussian 3"
+ rotations: torch.Tensor # world_quat_wxyz, "batch gaussian 4"
+ harmonics: torch.Tensor # world SH, "batch gaussian 3 d_sh"
+ opacities: torch.Tensor # opacity | opacity SH, "batch gaussian" | "batch gaussian 1 d_sh"
+
+
+@dataclass
+class Prediction:
+ depth: np.ndarray # N, H, W
+ is_metric: int
+ sky: np.ndarray | None = None # N, H, W
+ conf: np.ndarray | None = None # N, H, W
+ extrinsics: np.ndarray | None = None # N, 4, 4
+ intrinsics: np.ndarray | None = None # N, 3, 3
+ processed_images: np.ndarray | None = None # N, H, W, 3 - processed images for visualization
+ gaussians: Gaussians | None = None # 3D gaussians
+ aux: dict[str, Any] = None #
+ scale_factor: Optional[float] = None # metric scale
diff --git a/cameras/source/src/depth_anything_3/utils/alignment.py b/cameras/source/src/depth_anything_3/utils/alignment.py
new file mode 100644
index 0000000000000000000000000000000000000000..ceb8983fbb85fd8cad51fdd82b794aa3a114df78
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/alignment.py
@@ -0,0 +1,163 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Alignment utilities for depth estimation and metric scaling.
+"""
+
+from typing import Tuple
+import torch
+
+
+def least_squares_scale_scalar(
+ a: torch.Tensor, b: torch.Tensor, eps: float = 1e-12
+) -> torch.Tensor:
+ """
+ Compute least squares scale factor s such that a ≈ s * b.
+
+ Args:
+ a: First tensor
+ b: Second tensor
+ eps: Small epsilon for numerical stability
+
+ Returns:
+ Scalar tensor containing the scale factor
+
+ Raises:
+ ValueError: If tensors have mismatched shapes or devices
+ TypeError: If tensors are not floating point
+ """
+ if a.shape != b.shape:
+ raise ValueError(f"Shape mismatch: {a.shape} vs {b.shape}")
+ if a.device != b.device:
+ raise ValueError(f"Device mismatch: {a.device} vs {b.device}")
+ if not a.is_floating_point() or not b.is_floating_point():
+ raise TypeError("Tensors must be floating point type")
+
+ # Compute dot products for least squares solution
+ num = torch.dot(a.reshape(-1), b.reshape(-1))
+ den = torch.dot(b.reshape(-1), b.reshape(-1)).clamp_min(eps)
+ return num / den
+
+
+def compute_sky_mask(sky_prediction: torch.Tensor, threshold: float = 0.3) -> torch.Tensor:
+ """
+ Compute non-sky mask from sky prediction.
+
+ Args:
+ sky_prediction: Sky prediction tensor
+ threshold: Threshold for sky classification
+
+ Returns:
+ Boolean mask where True indicates non-sky regions
+ """
+ return sky_prediction < threshold
+
+
+def compute_alignment_mask(
+ depth_conf: torch.Tensor,
+ non_sky_mask: torch.Tensor,
+ depth: torch.Tensor,
+ metric_depth: torch.Tensor,
+ median_conf: torch.Tensor,
+ min_depth_threshold: float = 1e-3,
+ min_metric_depth_threshold: float = 1e-2,
+) -> torch.Tensor:
+ """
+ Compute mask for depth alignment based on confidence and depth thresholds.
+
+ Args:
+ depth_conf: Depth confidence tensor
+ non_sky_mask: Non-sky region mask
+ depth: Predicted depth tensor
+ metric_depth: Metric depth tensor
+ median_conf: Median confidence threshold
+ min_depth_threshold: Minimum depth threshold
+ min_metric_depth_threshold: Minimum metric depth threshold
+
+ Returns:
+ Boolean mask for valid alignment regions
+ """
+ return (
+ (depth_conf >= median_conf)
+ & non_sky_mask
+ & (metric_depth > min_metric_depth_threshold)
+ & (depth > min_depth_threshold)
+ )
+
+
+def sample_tensor_for_quantile(tensor: torch.Tensor, max_samples: int = 100000) -> torch.Tensor:
+ """
+ Sample tensor elements for quantile computation to reduce memory usage.
+
+ Args:
+ tensor: Input tensor to sample
+ max_samples: Maximum number of samples to take
+
+ Returns:
+ Sampled tensor
+ """
+ if tensor.numel() <= max_samples:
+ return tensor
+
+ idx = torch.randperm(tensor.numel(), device=tensor.device)[:max_samples]
+ return tensor.flatten()[idx]
+
+
+def apply_metric_scaling(
+ depth: torch.Tensor, intrinsics: torch.Tensor, scale_factor: float = 300.0
+) -> torch.Tensor:
+ """
+ Apply metric scaling to depth based on camera intrinsics.
+
+ Args:
+ depth: Input depth tensor
+ intrinsics: Camera intrinsics tensor
+ scale_factor: Scaling factor for metric conversion
+
+ Returns:
+ Scaled depth tensor
+ """
+ focal_length = (intrinsics[:, :, 0, 0] + intrinsics[:, :, 1, 1]) / 2
+ return depth * (focal_length[:, :, None, None] / scale_factor)
+
+
+def set_sky_regions_to_max_depth(
+ depth: torch.Tensor,
+ depth_conf: torch.Tensor,
+ non_sky_mask: torch.Tensor,
+ max_depth: float = 200.0,
+) -> Tuple[torch.Tensor, torch.Tensor]:
+ """
+ Set sky regions to maximum depth and high confidence.
+
+ Args:
+ depth: Depth tensor
+ depth_conf: Depth confidence tensor
+ non_sky_mask: Non-sky region mask
+ max_depth: Maximum depth value for sky regions
+
+ Returns:
+ Tuple of (updated_depth, updated_depth_conf)
+ """
+ depth = depth.clone()
+
+ # Set sky regions to max depth and high confidence
+ depth[~non_sky_mask] = max_depth
+ if depth_conf is not None:
+ depth_conf = depth_conf.clone()
+ depth_conf[~non_sky_mask] = 1.0
+ return depth, depth_conf
+ else:
+ return depth, None
diff --git a/cameras/source/src/depth_anything_3/utils/api_helpers.py b/cameras/source/src/depth_anything_3/utils/api_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..b327331d9ec61a3047be3e330f41156eb124adc4
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/api_helpers.py
@@ -0,0 +1,58 @@
+import argparse
+
+
+def parse_scalar(s):
+ if not isinstance(s, str):
+ return s
+ t = s.strip()
+ l = t.lower()
+ if l == "true":
+ return True
+ if l == "false":
+ return False
+ if l in ("none", "null"):
+ return None
+ try:
+ return int(t, 10)
+ except Exception:
+ pass
+ try:
+ return float(t)
+ except Exception:
+ return s
+
+
+def fn_kv_csv(s: str) -> dict[str, dict[str, object]]:
+ """
+ Parse a string of comma-separated triplets: fn:key:value
+
+ Returns:
+ dict[fn_name] -> dict[key] = parsed_value
+
+ Example:
+ "fn1:width:1920,fn1:height:1080,fn2:quality:0.8"
+ -> {"fn1": {"width": 1920, "height": 1080}, "fn2": {"quality": 0.8}}
+ """
+ result: dict[str, dict[str, object]] = {}
+ if not s:
+ return result
+
+ for item in s.split(","):
+ if not item:
+ continue
+ parts = item.split(":", 2) # allow value to contain ":" beyond first two separators
+ if len(parts) < 3:
+ raise argparse.ArgumentTypeError(f"Bad item '{item}', expected FN:KEY:VALUE")
+ fn, key, raw_val = parts[0], parts[1], parts[2]
+ # If you need to allow colons in values, join leftover parts:
+ # fn, key, raw_val = parts[0], parts[1], ":".join(parts[2:])
+
+ if not fn:
+ raise argparse.ArgumentTypeError(f"Bad item '{item}': empty function name")
+ if not key:
+ raise argparse.ArgumentTypeError(f"Bad item '{item}': empty key")
+
+ val = parse_scalar(raw_val)
+ bucket = result.setdefault(fn, {})
+ bucket[key] = val
+ return result
diff --git a/cameras/source/src/depth_anything_3/utils/camera_trj_helpers.py b/cameras/source/src/depth_anything_3/utils/camera_trj_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..83624f359553908abd28af2d59f2066a7f7a7b15
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/camera_trj_helpers.py
@@ -0,0 +1,479 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import cv2
+import numpy as np
+import torch
+import torch.nn.functional as F
+from einops import einsum, rearrange, reduce
+
+try:
+ from scipy.spatial.transform import Rotation as R
+except ImportError:
+ from depth_anything_3.utils.logger import logger
+
+ logger.warn("Dependency 'scipy' not found. Required for interpolating camera trajectory.")
+
+from depth_anything_3.utils.geometry import as_homogeneous
+
+
+@torch.no_grad()
+def render_stabilization_path(poses, k_size=45):
+ """Rendering stabilized camera path.
+ poses: [batch, 4, 4] or [batch, 3, 4],
+ return:
+ smooth path: [batch 4 4]"""
+ num_frames = poses.shape[0]
+ device = poses.device
+ dtype = poses.dtype
+
+ # Early exit for trivial cases
+ if num_frames <= 1:
+ return as_homogeneous(poses)
+
+ # Make k_size safe: positive odd and not larger than num_frames
+ # 1) Ensure odd
+ if k_size < 1:
+ k_size = 1
+ if k_size % 2 == 0:
+ k_size += 1
+ # 2) Cap to num_frames (keep odd)
+ max_odd = num_frames if (num_frames % 2 == 1) else (num_frames - 1)
+ if max_odd < 1:
+ max_odd = 1 # covers num_frames == 0 theoretically
+ k_size = min(k_size, max_odd)
+ # 3) enforce a minimum of 3 when possible (for better smoothing)
+ if num_frames >= 3 and k_size < 3:
+ k_size = 3
+
+ input_poses = []
+ for i in range(num_frames):
+ input_poses.append(
+ torch.cat([poses[i, :3, 0:1], poses[i, :3, 1:2], poses[i, :3, 3:4]], dim=-1)
+ )
+ input_poses = torch.stack(input_poses) # (num_frames, 3, 3)
+
+ # Prepare Gaussian kernel
+ gaussian_kernel = cv2.getGaussianKernel(ksize=k_size, sigma=-1).astype(np.float32).squeeze()
+ gaussian_kernel = torch.tensor(gaussian_kernel, dtype=dtype, device=device).view(1, 1, -1)
+ pad = k_size // 2
+
+ output_vectors = []
+ for idx in range(3): # For r1, r2, t
+ vec = (
+ input_poses[:, :, idx].T.unsqueeze(0).unsqueeze(0)
+ ) # (1, 1, 3, num_frames) -> (1, 1, 3, num_frames)
+ # But actually, we want (batch=3, channel=1, width=num_frames)
+ # So:
+ vec = input_poses[:, :, idx].T.unsqueeze(1) # (3, 1, num_frames)
+ vec_padded = F.pad(vec, (pad, pad), mode="reflect")
+ filtered = F.conv1d(vec_padded, gaussian_kernel)
+ output_vectors.append(filtered.squeeze(1).T) # (num_frames, 3)
+
+ output_r1, output_r2, output_t = output_vectors # Each is (num_frames, 3)
+
+ # Normalize r1 and r2
+ output_r1 = output_r1 / output_r1.norm(dim=-1, keepdim=True)
+ output_r2 = output_r2 / output_r2.norm(dim=-1, keepdim=True)
+
+ output_poses = []
+ for i in range(num_frames):
+ output_r3 = torch.linalg.cross(output_r1[i], output_r2[i])
+ render_pose = torch.cat(
+ [
+ output_r1[i].unsqueeze(-1),
+ output_r2[i].unsqueeze(-1),
+ output_r3.unsqueeze(-1),
+ output_t[i].unsqueeze(-1),
+ ],
+ dim=-1,
+ )
+ output_poses.append(render_pose[:3, :])
+ output_poses = as_homogeneous(torch.stack(output_poses, dim=0))
+
+ return output_poses
+
+
+@torch.no_grad()
+def render_wander_path(
+ cam2world: torch.Tensor,
+ intrinsic: torch.Tensor,
+ h: int,
+ w: int,
+ num_frames: int = 120,
+ max_disp: float = 48.0,
+):
+ device, dtype = cam2world.device, cam2world.dtype
+ fx = intrinsic[0, 0] * w
+ r = max_disp / fx
+ th = torch.linspace(0, 2.0 * torch.pi, steps=num_frames, device=device, dtype=dtype)
+ x = r * torch.sin(th)
+ yz = r * torch.cos(th) / 3.0
+ T = torch.eye(4, device=device, dtype=dtype).unsqueeze(0).repeat(num_frames, 1, 1)
+ T[:, :3, 3] = torch.stack([x, yz, yz], dim=-1) * -1.0
+ c2ws = cam2world.unsqueeze(0) @ T
+ # Start at reference pose and end back at reference pose
+ c2ws = torch.cat([cam2world.unsqueeze(0), c2ws, cam2world.unsqueeze(0)], dim=0)
+ Ks = intrinsic.unsqueeze(0).repeat(c2ws.shape[0], 1, 1)
+ return c2ws, Ks
+
+
+@torch.no_grad()
+def render_dolly_zoom_path(
+ cam2world: torch.Tensor,
+ intrinsic: torch.Tensor,
+ h: int,
+ w: int,
+ num_frames: int = 120,
+ max_disp: float = 0.1,
+ D_focus: float = 10.0,
+):
+ device, dtype = cam2world.device, cam2world.dtype
+ fx0, fy0 = intrinsic[0, 0] * w, intrinsic[1, 1] * h
+ t = torch.linspace(0.0, 2.0, steps=num_frames, device=device, dtype=dtype)
+ z = 0.5 * (1.0 - torch.cos(torch.pi * t)) * max_disp
+ T = torch.eye(4, device=device, dtype=dtype).unsqueeze(0).repeat(num_frames, 1, 1)
+ T[:, 2, 3] = -z
+ c2ws = cam2world.unsqueeze(0) @ T
+ Df = torch.as_tensor(D_focus, device=device, dtype=dtype)
+ scale = (Df / (Df + z)).clamp(min=1e-6)
+ Ks = intrinsic.unsqueeze(0).repeat(num_frames, 1, 1)
+ Ks[:, 0, 0] = (fx0 * scale) / w
+ Ks[:, 1, 1] = (fy0 * scale) / h
+ return c2ws, Ks
+
+
+@torch.no_grad()
+def interpolate_intrinsics(
+ initial: torch.Tensor, # "*#batch 3 3"
+ final: torch.Tensor, # "*#batch 3 3"
+ t: torch.Tensor, # " time_step"
+) -> torch.Tensor: # "*batch time_step 3 3"
+ initial = rearrange(initial, "... i j -> ... () i j")
+ final = rearrange(final, "... i j -> ... () i j")
+ t = rearrange(t, "t -> t () ()")
+ return initial + (final - initial) * t
+
+
+def intersect_rays(
+ a_origins: torch.Tensor, # "*#batch dim"
+ a_directions: torch.Tensor, # "*#batch dim"
+ b_origins: torch.Tensor, # "*#batch dim"
+ b_directions: torch.Tensor, # "*#batch dim"
+) -> torch.Tensor: # "*batch dim"
+ """Compute the least-squares intersection of rays. Uses the math from here:
+ https://math.stackexchange.com/a/1762491/286022
+ """
+
+ # Broadcast and stack the tensors.
+ a_origins, a_directions, b_origins, b_directions = torch.broadcast_tensors(
+ a_origins, a_directions, b_origins, b_directions
+ )
+ origins = torch.stack((a_origins, b_origins), dim=-2)
+ directions = torch.stack((a_directions, b_directions), dim=-2)
+
+ # Compute n_i * n_i^T - eye(3) from the equation.
+ n = einsum(directions, directions, "... n i, ... n j -> ... n i j")
+ n = n - torch.eye(3, dtype=origins.dtype, device=origins.device)
+
+ # Compute the left-hand side of the equation.
+ lhs = reduce(n, "... n i j -> ... i j", "sum")
+
+ # Compute the right-hand side of the equation.
+ rhs = einsum(n, origins, "... n i j, ... n j -> ... n i")
+ rhs = reduce(rhs, "... n i -> ... i", "sum")
+
+ # Left-matrix-multiply both sides by the inverse of lhs to find p.
+ return torch.linalg.lstsq(lhs, rhs).solution
+
+
+def normalize(a: torch.Tensor) -> torch.Tensor: # "*#batch dim" -> "*#batch dim"
+ return a / a.norm(dim=-1, keepdim=True)
+
+
+def generate_coordinate_frame(
+ y: torch.Tensor, # "*#batch 3"
+ z: torch.Tensor, # "*#batch 3"
+) -> torch.Tensor: # "*batch 3 3"
+ """Generate a coordinate frame given perpendicular, unit-length Y and Z vectors."""
+ y, z = torch.broadcast_tensors(y, z)
+ return torch.stack([y.cross(z, dim=-1), y, z], dim=-1)
+
+
+def generate_rotation_coordinate_frame(
+ a: torch.Tensor, # "*#batch 3"
+ b: torch.Tensor, # "*#batch 3"
+ eps: float = 1e-4,
+) -> torch.Tensor: # "*batch 3 3"
+ """Generate a coordinate frame where the Y direction is normal to the plane defined
+ by unit vectors a and b. The other axes are arbitrary."""
+ device = a.device
+
+ # Replace every entry in b that's parallel to the corresponding entry in a with an
+ # arbitrary vector.
+ b = b.detach().clone()
+ parallel = (einsum(a, b, "... i, ... i -> ...").abs() - 1).abs() < eps
+ b[parallel] = torch.tensor([0, 0, 1], dtype=b.dtype, device=device)
+ parallel = (einsum(a, b, "... i, ... i -> ...").abs() - 1).abs() < eps
+ b[parallel] = torch.tensor([0, 1, 0], dtype=b.dtype, device=device)
+
+ # Generate the coordinate frame. The initial cross product defines the plane.
+ return generate_coordinate_frame(normalize(torch.linalg.cross(a, b)), a)
+
+
+def matrix_to_euler(
+ rotations: torch.Tensor, # "*batch 3 3"
+ pattern: str,
+) -> torch.Tensor: # "*batch 3"
+ *batch, _, _ = rotations.shape
+ rotations = rotations.reshape(-1, 3, 3)
+ angles_np = R.from_matrix(rotations.detach().cpu().numpy()).as_euler(pattern)
+ rotations = torch.tensor(angles_np, dtype=rotations.dtype, device=rotations.device)
+ return rotations.reshape(*batch, 3)
+
+
+def euler_to_matrix(
+ rotations: torch.Tensor, # "*batch 3"
+ pattern: str,
+) -> torch.Tensor: # "*batch 3 3"
+ *batch, _ = rotations.shape
+ rotations = rotations.reshape(-1, 3)
+ matrix_np = R.from_euler(pattern, rotations.detach().cpu().numpy()).as_matrix()
+ rotations = torch.tensor(matrix_np, dtype=rotations.dtype, device=rotations.device)
+ return rotations.reshape(*batch, 3, 3)
+
+
+def extrinsics_to_pivot_parameters(
+ extrinsics: torch.Tensor, # "*#batch 4 4"
+ pivot_coordinate_frame: torch.Tensor, # "*#batch 3 3"
+ pivot_point: torch.Tensor, # "*#batch 3"
+) -> torch.Tensor: # "*batch 5"
+ """Convert the extrinsics to a representation with 5 degrees of freedom:
+ 1. Distance from pivot point in the "X" (look cross pivot axis) direction.
+ 2. Distance from pivot point in the "Y" (pivot axis) direction.
+ 3. Distance from pivot point in the Z (look) direction
+ 4. Angle in plane
+ 5. Twist (rotation not in plane)
+ """
+
+ # The pivot coordinate frame's Z axis is normal to the plane.
+ pivot_axis = pivot_coordinate_frame[..., :, 1]
+
+ # Compute the translation elements of the pivot parametrization.
+ translation_frame = generate_coordinate_frame(pivot_axis, extrinsics[..., :3, 2])
+ origin = extrinsics[..., :3, 3]
+ delta = pivot_point - origin
+ translation = einsum(translation_frame, delta, "... i j, ... i -> ... j")
+
+ # Add the rotation elements of the pivot parametrization.
+ inverted = pivot_coordinate_frame.inverse() @ extrinsics[..., :3, :3]
+ y, _, z = matrix_to_euler(inverted, "YXZ").unbind(dim=-1)
+
+ return torch.cat([translation, y[..., None], z[..., None]], dim=-1)
+
+
+def pivot_parameters_to_extrinsics(
+ parameters: torch.Tensor, # "*#batch 5"
+ pivot_coordinate_frame: torch.Tensor, # "*#batch 3 3"
+ pivot_point: torch.Tensor, # "*#batch 3"
+) -> torch.Tensor: # "*batch 4 4"
+ translation, y, z = parameters.split((3, 1, 1), dim=-1)
+
+ euler = torch.cat((y, torch.zeros_like(y), z), dim=-1)
+ rotation = pivot_coordinate_frame @ euler_to_matrix(euler, "YXZ")
+
+ # The pivot coordinate frame's Z axis is normal to the plane.
+ pivot_axis = pivot_coordinate_frame[..., :, 1]
+
+ translation_frame = generate_coordinate_frame(pivot_axis, rotation[..., :3, 2])
+ delta = einsum(translation_frame, translation, "... i j, ... j -> ... i")
+ origin = pivot_point - delta
+
+ *batch, _ = origin.shape
+ extrinsics = torch.eye(4, dtype=parameters.dtype, device=parameters.device)
+ extrinsics = extrinsics.broadcast_to((*batch, 4, 4)).clone()
+ extrinsics[..., 3, 3] = 1
+ extrinsics[..., :3, :3] = rotation
+ extrinsics[..., :3, 3] = origin
+ return extrinsics
+
+
+def interpolate_circular(
+ a: torch.Tensor, # "*#batch"
+ b: torch.Tensor, # "*#batch"
+ t: torch.Tensor, # "*#batch"
+) -> torch.Tensor: # " *batch"
+ a, b, t = torch.broadcast_tensors(a, b, t)
+
+ tau = 2 * torch.pi
+ a = a % tau
+ b = b % tau
+
+ # Consider piecewise edge cases.
+ d = (b - a).abs()
+ a_left = a - tau
+ d_left = (b - a_left).abs()
+ a_right = a + tau
+ d_right = (b - a_right).abs()
+ use_d = (d < d_left) & (d < d_right)
+ use_d_left = (d_left < d_right) & (~use_d)
+ use_d_right = (~use_d) & (~use_d_left)
+
+ result = a + (b - a) * t
+ result[use_d_left] = (a_left + (b - a_left) * t)[use_d_left]
+ result[use_d_right] = (a_right + (b - a_right) * t)[use_d_right]
+
+ return result
+
+
+def interpolate_pivot_parameters(
+ initial: torch.Tensor, # "*#batch 5"
+ final: torch.Tensor, # "*#batch 5"
+ t: torch.Tensor, # " time_step"
+) -> torch.Tensor: # "*batch time_step 5"
+ initial = rearrange(initial, "... d -> ... () d")
+ final = rearrange(final, "... d -> ... () d")
+ t = rearrange(t, "t -> t ()")
+ ti, ri = initial.split((3, 2), dim=-1)
+ tf, rf = final.split((3, 2), dim=-1)
+
+ t_lerp = ti + (tf - ti) * t
+ r_lerp = interpolate_circular(ri, rf, t)
+
+ return torch.cat((t_lerp, r_lerp), dim=-1)
+
+
+@torch.no_grad()
+def interpolate_extrinsics(
+ initial: torch.Tensor, # "*#batch 4 4"
+ final: torch.Tensor, # "*#batch 4 4"
+ t: torch.Tensor, # " time_step"
+ eps: float = 1e-4,
+) -> torch.Tensor: # "*batch time_step 4 4"
+ """Interpolate extrinsics by rotating around their "focus point," which is the
+ least-squares intersection between the look vectors of the initial and final
+ extrinsics.
+ """
+
+ initial = initial.type(torch.float64)
+ final = final.type(torch.float64)
+ t = t.type(torch.float64)
+
+ # Based on the dot product between the look vectors, pick from one of two cases:
+ # 1. Look vectors are parallel: interpolate about their origins' midpoint.
+ # 3. Look vectors aren't parallel: interpolate about their focus point.
+ initial_look = initial[..., :3, 2]
+ final_look = final[..., :3, 2]
+ dot_products = einsum(initial_look, final_look, "... i, ... i -> ...")
+ parallel_mask = (dot_products.abs() - 1).abs() < eps
+
+ # Pick focus points.
+ initial_origin = initial[..., :3, 3]
+ final_origin = final[..., :3, 3]
+ pivot_point = 0.5 * (initial_origin + final_origin)
+ pivot_point[~parallel_mask] = intersect_rays(
+ initial_origin[~parallel_mask],
+ initial_look[~parallel_mask],
+ final_origin[~parallel_mask],
+ final_look[~parallel_mask],
+ )
+
+ # Convert to pivot parameters.
+ pivot_frame = generate_rotation_coordinate_frame(initial_look, final_look, eps=eps)
+ initial_params = extrinsics_to_pivot_parameters(initial, pivot_frame, pivot_point)
+ final_params = extrinsics_to_pivot_parameters(final, pivot_frame, pivot_point)
+
+ # Interpolate the pivot parameters.
+ interpolated_params = interpolate_pivot_parameters(initial_params, final_params, t)
+
+ # Convert back.
+ return pivot_parameters_to_extrinsics(
+ interpolated_params.type(torch.float32),
+ rearrange(pivot_frame, "... i j -> ... () i j").type(torch.float32),
+ rearrange(pivot_point, "... xyz -> ... () xyz").type(torch.float32),
+ )
+
+
+@torch.no_grad()
+def generate_wobble_transformation(
+ radius: torch.Tensor, # "*#batch"
+ t: torch.Tensor, # " time_step"
+ num_rotations: int = 1,
+ scale_radius_with_t: bool = True,
+) -> torch.Tensor: # "*batch time_step 4 4"]:
+ # Generate a translation in the image plane.
+ tf = torch.eye(4, dtype=torch.float32, device=t.device)
+ tf = tf.broadcast_to((*radius.shape, t.shape[0], 4, 4)).clone()
+ radius = radius[..., None]
+ if scale_radius_with_t:
+ radius = radius * t
+ tf[..., 0, 3] = torch.sin(2 * torch.pi * num_rotations * t) * radius
+ tf[..., 1, 3] = -torch.cos(2 * torch.pi * num_rotations * t) * radius
+ return tf
+
+
+@torch.no_grad()
+def render_wobble_inter_path(
+ cam2world: torch.Tensor, intr_normed: torch.Tensor, inter_len: int, n_skip: int = 3
+):
+ """
+ cam2world: [batch, 4, 4],
+ intr_normed: [batch, 3, 3]
+ """
+ frame_per_round = n_skip * inter_len
+ num_rotations = 1
+
+ t = torch.linspace(0, 1, frame_per_round, dtype=torch.float32, device=cam2world.device)
+ # t = (torch.cos(torch.pi * (t + 1)) + 1) / 2
+ tgt_c2w_b = []
+ tgt_intr_b = []
+ for b_idx in range(cam2world.shape[0]):
+ tgt_c2w = []
+ tgt_intr = []
+ for cur_idx in range(0, cam2world.shape[1] - n_skip, n_skip):
+ origin_a = cam2world[b_idx, cur_idx, :3, 3]
+ origin_b = cam2world[b_idx, cur_idx + n_skip, :3, 3]
+ delta = (origin_a - origin_b).norm(dim=-1)
+ if cur_idx == 0:
+ delta_prev = delta
+ else:
+ delta = (delta_prev + delta) / 2
+ delta_prev = delta
+ tf = generate_wobble_transformation(
+ radius=delta * 0.5,
+ t=t,
+ num_rotations=num_rotations,
+ scale_radius_with_t=False,
+ )
+ cur_extrs = (
+ interpolate_extrinsics(
+ cam2world[b_idx, cur_idx],
+ cam2world[b_idx, cur_idx + n_skip],
+ t,
+ )
+ @ tf
+ )
+ tgt_c2w.append(cur_extrs[(0 if cur_idx == 0 else 1) :])
+ tgt_intr.append(
+ interpolate_intrinsics(
+ intr_normed[b_idx, cur_idx],
+ intr_normed[b_idx, cur_idx + n_skip],
+ t,
+ )[(0 if cur_idx == 0 else 1) :]
+ )
+ tgt_c2w_b.append(torch.cat(tgt_c2w))
+ tgt_intr_b.append(torch.cat(tgt_intr))
+ tgt_c2w = torch.stack(tgt_c2w_b) # b v 4 4
+ tgt_intr = torch.stack(tgt_intr_b) # b v 3 3
+ return tgt_c2w, tgt_intr
diff --git a/cameras/source/src/depth_anything_3/utils/constants.py b/cameras/source/src/depth_anything_3/utils/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..25c330ee00820f44df05aa624f0aa4763afe1164
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/constants.py
@@ -0,0 +1,270 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+DEFAULT_MODEL = "depth-anything/DA3NESTED-GIANT-LARGE-1.1"
+DEFAULT_EXPORT_DIR = "workspace/gallery/scene"
+DEFAULT_GALLERY_DIR = "workspace/gallery"
+DEFAULT_GRADIO_DIR = "workspace/gradio"
+THRESH_FOR_REF_SELECTION = 3
+
+# =============================================================================
+# Benchmark Evaluation Constants
+# =============================================================================
+
+# Default evaluation workspace directory
+DEFAULT_EVAL_WORKSPACE = "workspace/evaluation"
+
+# Default reference view selection strategy for evaluation
+# Use "first" for consistent and reproducible evaluation results
+# Other options: "saddle_balanced", "auto", "mid"
+EVAL_REF_VIEW_STRATEGY = "first"
+
+# -----------------------------------------------------------------------------
+# DTU Dataset Configuration
+# Reference: https://roboimagedata.compute.dtu.dk/
+# Note: DepthAnything3 was never trained on any images from DTU.
+# -----------------------------------------------------------------------------
+
+# Root directory for DTU evaluation data (MVSNet format)
+# Download from: https://drive.google.com/file/d/1rX0EXlUL4prRxrRu2DgLJv2j7-tpUD4D/view
+DTU_EVAL_DATA_ROOT = "workspace/benchmark_dataset/dtu"
+
+# List of DTU evaluation scenes
+DTU_SCENES = [
+ "scan1",
+ "scan4",
+ "scan9",
+ "scan10",
+ "scan11",
+ "scan12",
+ "scan13",
+ "scan15",
+ "scan23",
+ "scan24",
+ "scan29",
+ "scan32",
+ "scan33",
+ "scan34",
+ "scan48",
+ "scan49",
+ "scan62",
+ "scan75",
+ "scan77",
+ "scan110",
+ "scan114",
+ "scan118",
+]
+
+# Point cloud fusion hyperparameters
+DTU_DIST_THRESH = 0.2 # Distance threshold for geometric consistency (mm)
+DTU_NUM_CONSIST = 4 # Minimum number of consistent views for a point
+DTU_MAX_POINTS = 4_000_000 # Maximum points in fused point cloud
+
+# 3D reconstruction evaluation hyperparameters
+DTU_DOWN_DENSE = 0.2 # Downsample density for evaluation (mm)
+DTU_PATCH_SIZE = 60 # Patch size for boundary handling
+DTU_MAX_DIST = 20 # Outlier threshold for accuracy/completeness (mm)
+
+# -----------------------------------------------------------------------------
+# DTU-64 Dataset Configuration (Pose Evaluation Only)
+# This is a subset of DTU with 64 images per scene for pose evaluation.
+# Note: This dataset is ONLY for pose evaluation, not 3D reconstruction.
+# -----------------------------------------------------------------------------
+
+# Root directory for DTU-64 evaluation data
+DTU64_EVAL_DATA_ROOT = "workspace/benchmark_dataset/dtu64"
+DTU64_CAMERA_ROOT = "workspace/benchmark_dataset/dtu64/Cameras"
+
+# List of DTU-64 evaluation scenes (13 scenes)
+DTU64_SCENES = [
+ "scan105",
+ "scan114",
+ "scan118",
+ "scan122",
+ "scan24",
+ "scan37",
+ "scan40",
+ "scan55",
+ "scan63",
+ "scan65",
+ "scan69",
+ "scan83",
+ "scan97",
+]
+
+# -----------------------------------------------------------------------------
+# ETH3D Dataset Configuration
+# Reference: https://www.eth3d.net/
+# High-resolution multi-view stereo benchmark with laser-scanned ground truth.
+# Note: DepthAnything3 was never trained on any images from ETH3D.
+# -----------------------------------------------------------------------------
+
+# Root directory for ETH3D evaluation data
+ETH3D_EVAL_DATA_ROOT = "workspace/benchmark_dataset/eth3d"
+
+# List of ETH3D evaluation scenes (indoor and outdoor)
+ETH3D_SCENES = [
+ "courtyard",
+ "electro",
+ "kicker",
+ "pipes",
+ "relief",
+ # "terrace", # Excluded: known issues
+ "delivery_area",
+ "facade",
+ # "meadow", # Excluded: known issues
+ "office",
+ "playground",
+ "relief_2",
+ "terrains",
+]
+
+# Images to filter out (known problematic views per scene)
+ETH3D_FILTER_KEYS = {
+ "delivery_area": ["711.JPG", "712.JPG", "713.JPG", "714.JPG"],
+ "electro": ["9289.JPG", "9290.JPG", "9291.JPG", "9292.JPG", "9293.JPG", "9298.JPG"],
+ "playground": ["587.JPG", "588.JPG", "589.JPG", "590.JPG", "591.JPG", "592.JPG"],
+ "relief": [
+ "427.JPG", "428.JPG", "429.JPG", "430.JPG", "431.JPG", "432.JPG",
+ "433.JPG", "434.JPG", "435.JPG", "436.JPG", "437.JPG", "438.JPG",
+ ],
+ "relief_2": [
+ "458.JPG", "459.JPG", "460.JPG", "461.JPG", "462.JPG", "463.JPG",
+ "464.JPG", "465.JPG", "466.JPG", "467.JPG", "468.JPG",
+ ],
+}
+
+# TSDF fusion hyperparameters (scaled for outdoor scenes)
+ETH3D_VOXEL_LENGTH = 4.0 / 512.0 * 5 # Voxel size for TSDF (meters)
+ETH3D_SDF_TRUNC = 0.04 * 5 # SDF truncation distance (meters)
+ETH3D_MAX_DEPTH = 100000.0 # Maximum depth for integration (effectively no truncation)
+
+# Point cloud sampling
+ETH3D_SAMPLING_NUMBER = 1_000_000 # Number of points to sample from mesh
+
+# 3D reconstruction evaluation hyperparameters
+ETH3D_EVAL_THRESHOLD = 0.05 * 5 # Distance threshold for precision/recall (meters)
+ETH3D_DOWN_SAMPLE = 4.0 / 512.0 * 5 # Voxel size for evaluation downsampling (meters)
+
+
+# ==============================================================================
+# 7Scenes Dataset Configuration
+# ==============================================================================
+# Reference: https://www.microsoft.com/en-us/research/project/rgb-d-dataset-7-scenes/
+# Note: Indoor RGB-D dataset with ground truth poses and meshes.
+
+# Root directory for 7Scenes evaluation data
+SEVENSCENES_EVAL_DATA_ROOT = "workspace/benchmark_dataset/7scenes"
+
+# List of 7Scenes evaluation scenes
+SEVENSCENES_SCENES = [
+ "chess",
+ "fire",
+ "heads",
+ "office",
+ "pumpkin",
+ "redkitchen",
+ "stairs",
+]
+
+# Fixed camera intrinsics for 7Scenes (all images share same intrinsics)
+SEVENSCENES_FX = 585.0
+SEVENSCENES_FY = 585.0
+SEVENSCENES_CX = 320.0
+SEVENSCENES_CY = 240.0
+
+# TSDF fusion hyperparameters (indoor scenes, smaller voxels)
+SEVENSCENES_VOXEL_LENGTH = 4.0 / 512.0 # Voxel size for TSDF (meters)
+SEVENSCENES_SDF_TRUNC = 0.04 # SDF truncation distance (meters)
+SEVENSCENES_MAX_DEPTH = 1000000.0 # Maximum depth for integration (no truncation)
+
+# Point cloud sampling
+SEVENSCENES_SAMPLING_NUMBER = 1_000_000 # Number of points to sample from mesh
+
+# 3D reconstruction evaluation hyperparameters
+SEVENSCENES_EVAL_THRESHOLD = 0.05 # Distance threshold for precision/recall (meters)
+SEVENSCENES_DOWN_SAMPLE = 4.0 / 512.0 # Voxel size for evaluation downsampling (meters)
+
+
+# ==============================================================================
+# ScanNet++ Dataset Configuration
+# ==============================================================================
+# Reference: https://kaldir.vc.in.tum.de/scannetpp/
+# Note: High-quality indoor RGB-D dataset with iPhone and DSLR images.
+
+# Root directory for ScanNet++ evaluation data
+SCANNETPP_EVAL_DATA_ROOT = "workspace/benchmark_dataset/scannetpp"
+
+# List of ScanNet++ evaluation scenes
+SCANNETPP_SCENES = [
+ "09c1414f1b",
+ "1ada7a0617",
+ "40aec5fffa",
+ "3e8bba0176",
+ "acd95847c5",
+ "578511c8a9",
+ "5f99900f09",
+ "c4c04e6d6c",
+ "f3d64c30f8",
+ "7bc286c1b6",
+ "c5439f4607",
+ "286b55a2bf",
+ "fb5a96b1a2",
+ "7831862f02",
+ "38d58a7a31",
+ "bde1e479ad",
+ "9071e139d9",
+ "21d970d8de",
+ "bcd2436daf",
+ "cc5237fd77",
+]
+
+# Input resolution for ScanNet++ (after undistortion and resize)
+SCANNETPP_INPUT_H = 768
+SCANNETPP_INPUT_W = 1024
+
+# TSDF fusion hyperparameters (indoor scenes)
+SCANNETPP_VOXEL_LENGTH = 0.02 # Voxel size for TSDF (meters)
+SCANNETPP_SDF_TRUNC = 0.15 # SDF truncation distance (meters)
+SCANNETPP_MAX_DEPTH = 5.0 # Maximum depth for integration (meters)
+
+# Point cloud sampling
+SCANNETPP_SAMPLING_NUMBER = 1_000_000 # Number of points to sample from mesh
+
+# 3D reconstruction evaluation hyperparameters
+SCANNETPP_EVAL_THRESHOLD = 0.05 # Distance threshold for precision/recall (meters)
+SCANNETPP_DOWN_SAMPLE = 0.02 # Voxel size for evaluation downsampling (meters)
+
+
+# ==============================================================================
+# HiRoom Dataset Configuration
+# ==============================================================================
+# Note: Indoor RGB-D dataset.
+
+# Root directory for HiRoom evaluation data
+HIROOM_EVAL_DATA_ROOT = "workspace/benchmark_dataset/hiroom/data"
+HIROOM_GT_ROOT_PATH = "workspace/benchmark_dataset/hiroom/fused_pcd"
+HIROOM_SCENE_LIST_PATH = "workspace/benchmark_dataset/hiroom/selected_scene_list_val.txt"
+
+# TSDF fusion hyperparameters (indoor scenes)
+HIROOM_VOXEL_LENGTH = 4.0 / 512.0 # Voxel size for TSDF (meters)
+HIROOM_SDF_TRUNC = 0.04 # SDF truncation distance (meters)
+HIROOM_MAX_DEPTH = 10000.0 # Maximum depth for integration (no truncation)
+
+# Point cloud sampling
+HIROOM_SAMPLING_NUMBER = 1_000_000 # Number of points to sample from mesh
+
+# 3D reconstruction evaluation hyperparameters
+HIROOM_EVAL_THRESHOLD = 0.05 # Distance threshold for precision/recall (meters)
+HIROOM_DOWN_SAMPLE = 4.0 / 512.0 # Voxel size for evaluation downsampling (meters)
diff --git a/cameras/source/src/depth_anything_3/utils/export/__init__.py b/cameras/source/src/depth_anything_3/utils/export/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7073989b5048b6e42a6246e9dde2025ca1e130d
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/export/__init__.py
@@ -0,0 +1,75 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from depth_anything_3.specs import Prediction
+from depth_anything_3.utils.export.gs import export_to_gs_ply, export_to_gs_video
+
+from .colmap import export_to_colmap
+from .depth_vis import export_to_depth_vis
+from .feat_vis import export_to_feat_vis
+from .glb import export_to_glb
+from .npz import export_to_mini_npz, export_to_npz
+
+# Canonical set of export formats this dispatcher understands. Treat this as the
+# server-trusted enum for any request-facing validation (e.g. the HTTP backend) instead
+# of letting a client-supplied export_format string reach the dispatcher unchecked.
+SUPPORTED_EXPORT_FORMATS = frozenset(
+ {
+ "glb",
+ "mini_npz",
+ "npz",
+ "feat_vis",
+ "depth_vis",
+ "gs_ply",
+ "gs_video",
+ "colmap",
+ }
+)
+
+
+def export(
+ prediction: Prediction,
+ export_format: str,
+ export_dir: str,
+ **kwargs,
+):
+ if "-" in export_format:
+ export_formats = export_format.split("-")
+ for export_format in export_formats:
+ export(prediction, export_format, export_dir, **kwargs)
+ return # Prevent falling through to single-format handling
+
+ if export_format == "glb":
+ export_to_glb(prediction, export_dir, **kwargs.get(export_format, {}))
+ elif export_format == "mini_npz":
+ export_to_mini_npz(prediction, export_dir)
+ elif export_format == "npz":
+ export_to_npz(prediction, export_dir)
+ elif export_format == "feat_vis":
+ export_to_feat_vis(prediction, export_dir, **kwargs.get(export_format, {}))
+ elif export_format == "depth_vis":
+ export_to_depth_vis(prediction, export_dir)
+ elif export_format == "gs_ply":
+ export_to_gs_ply(prediction, export_dir, **kwargs.get(export_format, {}))
+ elif export_format == "gs_video":
+ export_to_gs_video(prediction, export_dir, **kwargs.get(export_format, {}))
+ elif export_format == "colmap":
+ export_to_colmap(prediction, export_dir, **kwargs.get(export_format, {}))
+ else:
+ raise ValueError(f"Unsupported export format: {export_format}")
+
+
+__all__ = [
+ export,
+]
diff --git a/cameras/source/src/depth_anything_3/utils/export/colmap.py b/cameras/source/src/depth_anything_3/utils/export/colmap.py
new file mode 100644
index 0000000000000000000000000000000000000000..e57964cc3302d3f348d91d0b0ecb5d4fe8c0a54b
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/export/colmap.py
@@ -0,0 +1,150 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import pycolmap
+import cv2 as cv
+import numpy as np
+
+from PIL import Image
+
+from depth_anything_3.specs import Prediction
+from depth_anything_3.utils.logger import logger
+
+from .glb import _depths_to_world_points_with_colors
+
+
+def export_to_colmap(
+ prediction: Prediction,
+ export_dir: str,
+ image_paths: list[str],
+ conf_thresh_percentile: float = 40.0,
+ process_res_method: str = "upper_bound_resize",
+) -> None:
+ # 1. Data preparation
+ conf_thresh = np.percentile(prediction.conf, conf_thresh_percentile)
+ points, colors = _depths_to_world_points_with_colors(
+ prediction.depth,
+ prediction.intrinsics,
+ prediction.extrinsics, # w2c
+ prediction.processed_images,
+ prediction.conf,
+ conf_thresh,
+ )
+ num_points = len(points)
+ logger.info(f"Exporting to COLMAP with {num_points} points")
+ num_frames = len(prediction.processed_images)
+ h, w = prediction.processed_images.shape[1:3]
+ points_xyf = _create_xyf(num_frames, h, w)
+ points_xyf = points_xyf[prediction.conf >= conf_thresh]
+
+ # 2. Set Reconstruction
+ reconstruction = pycolmap.Reconstruction()
+
+ point3d_ids = []
+ for vidx in range(num_points):
+ point3d_id = reconstruction.add_point3D(points[vidx], pycolmap.Track(), colors[vidx])
+ point3d_ids.append(point3d_id)
+
+ for fidx in range(num_frames):
+ orig_w, orig_h = Image.open(image_paths[fidx]).size
+
+ intrinsic = prediction.intrinsics[fidx]
+ if process_res_method.endswith("resize"):
+ intrinsic[:1] *= orig_w / w
+ intrinsic[1:2] *= orig_h / h
+ elif process_res_method == "crop":
+ raise NotImplementedError("COLMAP export for crop method is not implemented")
+ else:
+ raise ValueError(f"Unknown process_res_method: {process_res_method}")
+
+ pycolmap_intri = np.array(
+ [intrinsic[0, 0], intrinsic[1, 1], intrinsic[0, 2], intrinsic[1, 2]]
+ )
+
+ extrinsic = prediction.extrinsics[fidx]
+ cam_from_world = pycolmap.Rigid3d(pycolmap.Rotation3d(extrinsic[:3, :3]), extrinsic[:3, 3])
+
+ # set and add camera
+ camera = pycolmap.Camera()
+ camera.camera_id = fidx + 1
+ camera.model = pycolmap.CameraModelId.PINHOLE
+ camera.width = orig_w
+ camera.height = orig_h
+ camera.params = pycolmap_intri
+ reconstruction.add_camera(camera)
+
+ # set and add rig (from camera)
+ rig = pycolmap.Rig()
+ rig.rig_id = camera.camera_id
+ rig.add_ref_sensor(camera.sensor_id)
+ reconstruction.add_rig(rig)
+
+ # set image
+ image = pycolmap.Image()
+ image.image_id = fidx + 1
+ image.camera_id = camera.camera_id
+
+ # set and add frame (from image)
+ frame = pycolmap.Frame()
+ frame.frame_id = image.image_id
+ frame.rig_id = camera.camera_id
+ frame.add_data_id(image.data_id)
+ frame.rig_from_world = cam_from_world
+ reconstruction.add_frame(frame)
+
+ # set point2d and update track
+ point2d_list = []
+ points_in_frame = points_xyf[:, 2].astype(np.int32) == fidx
+ for vidx in np.where(points_in_frame)[0]:
+ point2d = points_xyf[vidx][:2]
+ point2d[0] *= orig_w / w
+ point2d[1] *= orig_h / h
+ point3d_id = point3d_ids[vidx]
+ point2d_list.append(pycolmap.Point2D(point2d, point3d_id))
+ reconstruction.point3D(point3d_id).track.add_element(
+ image.image_id, len(point2d_list) - 1
+ )
+
+ # set and add image
+ image.frame_id = image.image_id
+ image.name = os.path.basename(image_paths[fidx])
+ image.points2D = pycolmap.Point2DList(point2d_list)
+ reconstruction.add_image(image)
+
+ # 3. Export
+ reconstruction.write(export_dir)
+
+
+def _create_xyf(num_frames, height, width):
+ """
+ Creates a grid of pixel coordinates and frame indices (fidx) for all frames.
+ """
+ # Create coordinate grids for a single frame
+ y_grid, x_grid = np.indices((height, width), dtype=np.int32)
+ x_grid = x_grid[np.newaxis, :, :]
+ y_grid = y_grid[np.newaxis, :, :]
+
+ # Broadcast to all frames
+ x_coords = np.broadcast_to(x_grid, (num_frames, height, width))
+ y_coords = np.broadcast_to(y_grid, (num_frames, height, width))
+
+ # Create frame indices and broadcast
+ f_idx = np.arange(num_frames, dtype=np.int32)[:, np.newaxis, np.newaxis]
+ f_coords = np.broadcast_to(f_idx, (num_frames, height, width))
+
+ # Stack coordinates and frame indices
+ points_xyf = np.stack((x_coords, y_coords, f_coords), axis=-1)
+
+ return points_xyf
diff --git a/cameras/source/src/depth_anything_3/utils/export/depth_vis.py b/cameras/source/src/depth_anything_3/utils/export/depth_vis.py
new file mode 100644
index 0000000000000000000000000000000000000000..8accc04e92985e26b8d78a56db80989be515aac7
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/export/depth_vis.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import imageio
+import numpy as np
+
+from depth_anything_3.specs import Prediction
+from depth_anything_3.utils.visualize import visualize_depth
+
+
+def export_to_depth_vis(
+ prediction: Prediction,
+ export_dir: str,
+):
+ # Use prediction.processed_images, which is already processed image data
+ if prediction.processed_images is None:
+ raise ValueError("prediction.processed_images is required but not available")
+
+ images_u8 = prediction.processed_images # (N,H,W,3) uint8
+
+ os.makedirs(os.path.join(export_dir, "depth_vis"), exist_ok=True)
+ for idx in range(prediction.depth.shape[0]):
+ depth_vis = visualize_depth(prediction.depth[idx])
+ image_vis = images_u8[idx]
+ depth_vis = depth_vis.astype(np.uint8)
+ image_vis = image_vis.astype(np.uint8)
+ vis_image = np.concatenate([image_vis, depth_vis], axis=1)
+ save_path = os.path.join(export_dir, f"depth_vis/{idx:04d}.jpg")
+ imageio.imwrite(save_path, vis_image, quality=95)
diff --git a/cameras/source/src/depth_anything_3/utils/export/feat_vis.py b/cameras/source/src/depth_anything_3/utils/export/feat_vis.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2507f6e675c0a546311ae292b9f6859d218d8e2
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/export/feat_vis.py
@@ -0,0 +1,75 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import subprocess
+import cv2
+import imageio
+import numpy as np
+from tqdm.auto import tqdm
+
+from depth_anything_3.utils.logger import logger
+from depth_anything_3.utils.parallel_utils import async_call
+from depth_anything_3.utils.pca_utils import PCARGBVisualizer
+
+
+@async_call
+def export_to_feat_vis(
+ prediction,
+ export_dir,
+ fps=15,
+):
+ """Export feature visualization with PCA.
+
+ Args:
+ prediction: Model prediction containing feature maps
+ export_dir: Directory to export results
+ fps: Frame rate for output video (default: 15)
+ """
+ out_dir = os.path.join(export_dir, "feat_vis")
+ os.makedirs(out_dir, exist_ok=True)
+
+ images = prediction.processed_images
+ for k, v in prediction.aux.items():
+ if not k.startswith("feat_layer_"):
+ continue
+ os.makedirs(os.path.join(out_dir, k), exist_ok=True)
+ viz = PCARGBVisualizer(basis_mode="fixed", percentile_mode="global", clip_percent=10.0)
+ viz.fit_reference(v)
+ feats_vis = viz.transform_video(v)
+ for idx in tqdm(range(len(feats_vis))):
+ img = images[idx]
+ feat_vis = (feats_vis[idx] * 255).astype(np.uint8)
+ feat_vis = cv2.resize(
+ feat_vis, (img.shape[1], img.shape[0]), interpolation=cv2.INTER_NEAREST
+ )
+ save_path = os.path.join(out_dir, f"{k}/{idx:06d}.jpg")
+ save = np.concatenate([img, feat_vis], axis=1)
+ imageio.imwrite(save_path, save, quality=95)
+ cmd = [
+ "ffmpeg",
+ "-loglevel", "error",
+ "-hide_banner",
+ "-y",
+ "-framerate", str(fps),
+ "-start_number", "0",
+ "-i", os.path.join(out_dir, k, "%06d.jpg"),
+ "-c:v", "libx264",
+ "-pix_fmt", "yuv420p",
+ os.path.join(out_dir, f"{k}.mp4"),
+ ]
+ try:
+ subprocess.run(cmd, shell=False, check=False)
+ except OSError as e:
+ logger.warn(f"Failed to run ffmpeg for feature visualization export: {e}")
diff --git a/cameras/source/src/depth_anything_3/utils/export/glb.py b/cameras/source/src/depth_anything_3/utils/export/glb.py
new file mode 100644
index 0000000000000000000000000000000000000000..782106a0689cf763eefaec3c327a625c98e342cb
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/export/glb.py
@@ -0,0 +1,437 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+
+import os
+import shutil
+import numpy as np
+import trimesh
+
+from depth_anything_3.specs import Prediction
+from depth_anything_3.utils.logger import logger
+
+from .depth_vis import export_to_depth_vis
+
+
+def set_sky_depth(prediction: Prediction, sky_mask: np.ndarray, sky_depth_def: float = 98.0):
+ non_sky_mask = ~sky_mask
+ valid_depth = prediction.depth[non_sky_mask]
+ if valid_depth.size > 0:
+ max_depth = np.percentile(valid_depth, sky_depth_def)
+ prediction.depth[sky_mask] = max_depth
+
+
+def get_conf_thresh(
+ prediction: Prediction,
+ sky_mask: np.ndarray,
+ conf_thresh: float,
+ conf_thresh_percentile: float = 10.0,
+ ensure_thresh_percentile: float = 90.0,
+):
+ if sky_mask is not None and (~sky_mask).sum() > 10:
+ conf_pixels = prediction.conf[~sky_mask]
+ else:
+ conf_pixels = prediction.conf
+ lower = np.percentile(conf_pixels, conf_thresh_percentile)
+ upper = np.percentile(conf_pixels, ensure_thresh_percentile)
+ conf_thresh = min(max(conf_thresh, lower), upper)
+ return conf_thresh
+
+
+def export_to_glb(
+ prediction: Prediction,
+ export_dir: str,
+ num_max_points: int = 1_000_000,
+ conf_thresh: float = 1.05,
+ filter_black_bg: bool = False,
+ filter_white_bg: bool = False,
+ conf_thresh_percentile: float = 40.0,
+ ensure_thresh_percentile: float = 90.0,
+ sky_depth_def: float = 98.0,
+ show_cameras: bool = True,
+ camera_size: float = 0.03,
+ export_depth_vis: bool = True,
+) -> str:
+ """Generate a 3D point cloud and camera wireframes and export them as a ``.glb`` file.
+
+ The function builds a point cloud from the predicted depth maps, aligns it to the
+ first camera in glTF coordinates (X-right, Y-up, Z-backward), optionally draws
+ camera wireframes, and writes the result to ``scene.glb``. Auxiliary assets such as
+ depth visualizations can also be generated alongside the main export.
+
+ Args:
+ prediction: Model prediction containing depth, confidence, intrinsics, extrinsics,
+ and pre-processed images.
+ export_dir: Output directory where the glTF assets will be written.
+ num_max_points: Maximum number of points retained after downsampling.
+ conf_thresh: Base confidence threshold used before percentile adjustments.
+ filter_black_bg: Mark near-black background pixels for removal during confidence filtering.
+ filter_white_bg: Mark near-white background pixels for removal during confidence filtering.
+ conf_thresh_percentile: Lower percentile used when adapting the confidence threshold.
+ ensure_thresh_percentile: Upper percentile clamp for the adaptive threshold.
+ sky_depth_def: Percentile used to fill sky pixels with plausible depth values.
+ show_cameras: Whether to render camera wireframes in the exported scene.
+ camera_size: Relative camera wireframe scale as a fraction of the scene diagonal.
+ export_depth_vis: Whether to export raster depth visualisations alongside the glTF.
+
+ Returns:
+ Path to the exported ``scene.glb`` file.
+ """
+ # 1) Use prediction.processed_images, which is already processed image data
+ assert (
+ prediction.processed_images is not None
+ ), "Export to GLB: prediction.processed_images is required but not available"
+ assert (
+ prediction.depth is not None
+ ), "Export to GLB: prediction.depth is required but not available"
+ assert (
+ prediction.intrinsics is not None
+ ), "Export to GLB: prediction.intrinsics is required but not available"
+ assert (
+ prediction.extrinsics is not None
+ ), "Export to GLB: prediction.extrinsics is required but not available"
+ assert (
+ prediction.conf is not None
+ ), "Export to GLB: prediction.conf is required but not available"
+ logger.info(f"conf_thresh_percentile: {conf_thresh_percentile}")
+ logger.info(f"num max points: {num_max_points}")
+ logger.info(f"Exporting to GLB with num_max_points: {num_max_points}")
+ if prediction.processed_images is None:
+ raise ValueError("prediction.processed_images is required but not available")
+
+ images_u8 = prediction.processed_images # (N,H,W,3) uint8
+
+ # 2) Sky processing (if sky_mask is provided)
+ if getattr(prediction, "sky_mask", None) is not None:
+ set_sky_depth(prediction, prediction.sky_mask, sky_depth_def)
+
+ # 3) Confidence threshold (if no conf, then no filtering)
+ if filter_black_bg:
+ prediction.conf[(prediction.processed_images < 16).all(axis=-1)] = 1.0
+ if filter_white_bg:
+ prediction.conf[(prediction.processed_images >= 240).all(axis=-1)] = 1.0
+ conf_thr = get_conf_thresh(
+ prediction,
+ getattr(prediction, "sky_mask", None),
+ conf_thresh,
+ conf_thresh_percentile,
+ ensure_thresh_percentile,
+ )
+
+ # 4) Back-project to world coordinates and get colors (world frame)
+ points, colors = _depths_to_world_points_with_colors(
+ prediction.depth,
+ prediction.intrinsics,
+ prediction.extrinsics, # w2c
+ images_u8,
+ prediction.conf,
+ conf_thr,
+ )
+
+ # 5) Based on first camera orientation + glTF axis system, center by point cloud,
+ # construct alignment transform, and apply to point cloud
+ A = _compute_alignment_transform_first_cam_glTF_center_by_points(
+ prediction.extrinsics[0], points
+ ) # (4,4)
+
+ if points.shape[0] > 0:
+ points = trimesh.transform_points(points, A)
+
+ # 6) Clean + downsample
+ points, colors = _filter_and_downsample(points, colors, num_max_points)
+
+ # 7) Assemble scene (add point cloud first)
+ scene = trimesh.Scene()
+ if scene.metadata is None:
+ scene.metadata = {}
+ scene.metadata["hf_alignment"] = A # For camera wireframes and external reuse
+
+ if points.shape[0] > 0:
+ pc = trimesh.points.PointCloud(vertices=points, colors=colors)
+ scene.add_geometry(pc)
+
+ # 8) Draw cameras (wireframe pyramids), using the same transform A
+ if show_cameras and prediction.intrinsics is not None and prediction.extrinsics is not None:
+ scene_scale = _estimate_scene_scale(points, fallback=1.0)
+ H, W = prediction.depth.shape[1:]
+ _add_cameras_to_scene(
+ scene=scene,
+ K=prediction.intrinsics,
+ ext_w2c=prediction.extrinsics,
+ image_sizes=[(H, W)] * prediction.depth.shape[0],
+ scale=scene_scale * camera_size,
+ )
+
+ # 9) Export
+ os.makedirs(export_dir, exist_ok=True)
+ out_path = os.path.join(export_dir, "scene.glb")
+ scene.export(out_path)
+
+ if export_depth_vis:
+ export_to_depth_vis(prediction, export_dir)
+ depth_vis_thumbnail = os.path.join(export_dir, "depth_vis", "0000.jpg")
+ if os.path.isfile(depth_vis_thumbnail):
+ shutil.copy2(depth_vis_thumbnail, os.path.join(export_dir, "scene.jpg"))
+ else:
+ logger.warn(f"Depth visualization thumbnail not found at {depth_vis_thumbnail}")
+ return out_path
+
+
+# =========================
+# utilities
+# =========================
+
+
+def _as_homogeneous44(ext: np.ndarray) -> np.ndarray:
+ """
+ Accept (4,4) or (3,4) extrinsic parameters, return (4,4) homogeneous matrix.
+ """
+ if ext.shape == (4, 4):
+ return ext
+ if ext.shape == (3, 4):
+ H = np.eye(4, dtype=ext.dtype)
+ H[:3, :4] = ext
+ return H
+ raise ValueError(f"extrinsic must be (4,4) or (3,4), got {ext.shape}")
+
+
+def _depths_to_world_points_with_colors(
+ depth: np.ndarray,
+ K: np.ndarray,
+ ext_w2c: np.ndarray,
+ images_u8: np.ndarray,
+ conf: np.ndarray | None,
+ conf_thr: float,
+) -> tuple[np.ndarray, np.ndarray]:
+ """
+ For each frame, transform (u,v,1) through K^{-1} to get rays,
+ multiply by depth to camera frame, then use (w2c)^{-1} to transform to world frame.
+ Simultaneously extract colors.
+ """
+ N, H, W = depth.shape
+ us, vs = np.meshgrid(np.arange(W), np.arange(H))
+ ones = np.ones_like(us)
+ pix = np.stack([us, vs, ones], axis=-1).reshape(-1, 3) # (H*W,3)
+
+ pts_all, col_all = [], []
+
+ for i in range(N):
+ d = depth[i] # (H,W)
+ valid = np.isfinite(d) & (d > 0)
+ if conf is not None:
+ valid &= conf[i] >= conf_thr
+ if not np.any(valid):
+ continue
+
+ d_flat = d.reshape(-1)
+ vidx = np.flatnonzero(valid.reshape(-1))
+
+ K_inv = np.linalg.inv(K[i]) # (3,3)
+ c2w = np.linalg.inv(_as_homogeneous44(ext_w2c[i])) # (4,4)
+
+ rays = K_inv @ pix[vidx].T # (3,M)
+ Xc = rays * d_flat[vidx][None, :] # (3,M)
+ Xc_h = np.vstack([Xc, np.ones((1, Xc.shape[1]))])
+ Xw = (c2w @ Xc_h)[:3].T.astype(np.float32) # (M,3)
+
+ cols = images_u8[i].reshape(-1, 3)[vidx].astype(np.uint8) # (M,3)
+
+ pts_all.append(Xw)
+ col_all.append(cols)
+
+ if len(pts_all) == 0:
+ return np.zeros((0, 3), dtype=np.float32), np.zeros((0, 3), dtype=np.uint8)
+
+ return np.concatenate(pts_all, 0), np.concatenate(col_all, 0)
+
+
+def _filter_and_downsample(points: np.ndarray, colors: np.ndarray, num_max: int):
+ if points.shape[0] == 0:
+ return points, colors
+ finite = np.isfinite(points).all(axis=1)
+ points, colors = points[finite], colors[finite]
+ if points.shape[0] > num_max:
+ idx = np.random.choice(points.shape[0], num_max, replace=False)
+ points, colors = points[idx], colors[idx]
+ return points, colors
+
+
+def _estimate_scene_scale(points: np.ndarray, fallback: float = 1.0) -> float:
+ if points.shape[0] < 2:
+ return fallback
+ lo = np.percentile(points, 5, axis=0)
+ hi = np.percentile(points, 95, axis=0)
+ diag = np.linalg.norm(hi - lo)
+ return float(diag if np.isfinite(diag) and diag > 0 else fallback)
+
+
+def _compute_alignment_transform_first_cam_glTF_center_by_points(
+ ext_w2c0: np.ndarray,
+ points_world: np.ndarray,
+) -> np.ndarray:
+ """Computes the transformation matrix to align the scene with glTF standards.
+
+ This function calculates a 4x4 homogeneous matrix that centers the scene's
+ point cloud and transforms its coordinate system from the computer vision (CV)
+ standard to the glTF standard.
+
+ The transformation process involves three main steps:
+ 1. **Initial Alignment**: Orients the world coordinate system to match the
+ first camera's view (x-right, y-down, z-forward).
+ 2. **Coordinate System Conversion**: Converts the CV camera frame to the
+ glTF frame (x-right, y-up, z-backward) by flipping the Y and Z axes.
+ 3. **Centering**: Translates the entire scene so that the median of the
+ point cloud becomes the new origin (0,0,0).
+
+ Returns:
+ A 4x4 homogeneous transformation matrix (torch.Tensor or np.ndarray)
+ that applies these transformations. A: X' = A @ [X;1]
+ """
+
+ w2c0 = _as_homogeneous44(ext_w2c0).astype(np.float64)
+
+ # CV -> glTF axis transformation
+ M = np.eye(4, dtype=np.float64)
+ M[1, 1] = -1.0 # flip Y
+ M[2, 2] = -1.0 # flip Z
+
+ # Don't center first
+ A_no_center = M @ w2c0
+
+ # Calculate point cloud center in new coordinate system (use median to resist outliers)
+ if points_world.shape[0] > 0:
+ pts_tmp = trimesh.transform_points(points_world, A_no_center)
+ center = np.median(pts_tmp, axis=0)
+ else:
+ center = np.zeros(3, dtype=np.float64)
+
+ T_center = np.eye(4, dtype=np.float64)
+ T_center[:3, 3] = -center
+
+ A = T_center @ A_no_center
+ return A
+
+
+def _add_cameras_to_scene(
+ scene: trimesh.Scene,
+ K: np.ndarray,
+ ext_w2c: np.ndarray,
+ image_sizes: list[tuple[int, int]],
+ scale: float,
+) -> None:
+ """Draws camera frustums to visualize their position and orientation.
+
+ This function renders each camera as a wireframe pyramid, originating from
+ the camera's center and extending to the corners of its imaging plane.
+
+ It reads the 'hf_alignment' metadata from the scene to ensure the
+ wireframes are correctly aligned with the 3D point cloud.
+ """
+ N = K.shape[0]
+ if N == 0:
+ return
+
+ # Alignment matrix consistent with point cloud (use identity matrix if missing)
+ A = None
+ try:
+ A = scene.metadata.get("hf_alignment", None) if scene.metadata else None
+ except Exception:
+ A = None
+ if A is None:
+ A = np.eye(4, dtype=np.float64)
+
+ for i in range(N):
+ H, W = image_sizes[i]
+ segs = _camera_frustum_lines(K[i], ext_w2c[i], W, H, scale) # (8,2,3) world frame
+ # Apply unified transformation
+ segs = trimesh.transform_points(segs.reshape(-1, 3), A).reshape(-1, 2, 3)
+ path = trimesh.load_path(segs)
+ color = _index_color_rgb(i, N)
+ if hasattr(path, "colors"):
+ path.colors = np.tile(color, (len(path.entities), 1))
+ scene.add_geometry(path)
+
+
+def _camera_frustum_lines(
+ K: np.ndarray, ext_w2c: np.ndarray, W: int, H: int, scale: float
+) -> np.ndarray:
+ corners = np.array(
+ [
+ [0, 0, 1.0],
+ [W - 1, 0, 1.0],
+ [W - 1, H - 1, 1.0],
+ [0, H - 1, 1.0],
+ ],
+ dtype=float,
+ ) # (4,3)
+
+ K_inv = np.linalg.inv(K)
+ c2w = np.linalg.inv(_as_homogeneous44(ext_w2c))
+
+ # camera center in world
+ Cw = (c2w @ np.array([0, 0, 0, 1.0]))[:3]
+
+ # rays -> z=1 plane points (camera frame)
+ rays = (K_inv @ corners.T).T
+ z = rays[:, 2:3]
+ z[z == 0] = 1.0
+ plane_cam = (rays / z) * scale # (4,3)
+
+ # to world
+ plane_w = []
+ for p in plane_cam:
+ pw = (c2w @ np.array([p[0], p[1], p[2], 1.0]))[:3]
+ plane_w.append(pw)
+ plane_w = np.stack(plane_w, 0) # (4,3)
+
+ segs = []
+ # center to corners
+ for k in range(4):
+ segs.append(np.stack([Cw, plane_w[k]], 0))
+ # rectangle edges
+ order = [0, 1, 2, 3, 0]
+ for a, b in zip(order[:-1], order[1:]):
+ segs.append(np.stack([plane_w[a], plane_w[b]], 0))
+
+ return np.stack(segs, 0) # (8,2,3)
+
+
+def _index_color_rgb(i: int, n: int) -> np.ndarray:
+ h = (i + 0.5) / max(n, 1)
+ s, v = 0.85, 0.95
+ r, g, b = _hsv_to_rgb(h, s, v)
+ return (np.array([r, g, b]) * 255).astype(np.uint8)
+
+
+def _hsv_to_rgb(h: float, s: float, v: float) -> tuple[float, float, float]:
+ i = int(h * 6.0)
+ f = h * 6.0 - i
+ p = v * (1.0 - s)
+ q = v * (1.0 - f * s)
+ t = v * (1.0 - (1.0 - f) * s)
+ i = i % 6
+ if i == 0:
+ r, g, b = v, t, p
+ elif i == 1:
+ r, g, b = q, v, p
+ elif i == 2:
+ r, g, b = p, v, t
+ elif i == 3:
+ r, g, b = p, q, v
+ elif i == 4:
+ r, g, b = t, p, v
+ else:
+ r, g, b = v, p, q
+ return r, g, b
diff --git a/cameras/source/src/depth_anything_3/utils/export/gs.py b/cameras/source/src/depth_anything_3/utils/export/gs.py
new file mode 100644
index 0000000000000000000000000000000000000000..90077cf25651c7977c1c1da320b7c0931fdb18ba
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/export/gs.py
@@ -0,0 +1,154 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+from typing import Literal, Optional
+import moviepy.editor as mpy
+import torch
+
+from depth_anything_3.model.utils.gs_renderer import run_renderer_in_chunk_w_trj_mode
+from depth_anything_3.specs import Prediction
+from depth_anything_3.utils.gsply_helpers import save_gaussian_ply
+from depth_anything_3.utils.layout_helpers import hcat, vcat
+from depth_anything_3.utils.visualize import vis_depth_map_tensor
+
+VIDEO_QUALITY_MAP = {
+ "low": {"crf": "28", "preset": "veryfast"},
+ "medium": {"crf": "23", "preset": "medium"},
+ "high": {"crf": "18", "preset": "slow"},
+}
+
+
+def export_to_gs_ply(
+ prediction: Prediction,
+ export_dir: str,
+ gs_views_interval: Optional[
+ int
+ ] = 1, # export GS every N views, useful for extremely dense inputs
+):
+ gs_world = prediction.gaussians
+ pred_depth = torch.from_numpy(prediction.depth).unsqueeze(-1).to(gs_world.means) # v h w 1
+ idx = 0
+ os.makedirs(os.path.join(export_dir, "gs_ply"), exist_ok=True)
+ save_path = os.path.join(export_dir, f"gs_ply/{idx:04d}.ply")
+ if gs_views_interval is None: # select around 12 views in total
+ gs_views_interval = max(pred_depth.shape[0] // 12, 1)
+ save_gaussian_ply(
+ gaussians=gs_world,
+ save_path=save_path,
+ ctx_depth=pred_depth,
+ shift_and_scale=False,
+ save_sh_dc_only=True,
+ gs_views_interval=gs_views_interval,
+ inv_opacity=True,
+ prune_by_depth_percent=0.9,
+ prune_border_gs=True,
+ match_3dgs_mcmc_dev=False,
+ )
+
+
+def export_to_gs_video(
+ prediction: Prediction,
+ export_dir: str,
+ extrinsics: Optional[torch.Tensor] = None, # render views' world2cam, "b v 4 4"
+ intrinsics: Optional[torch.Tensor] = None, # render views' unnormed intrinsics, "b v 3 3"
+ out_image_hw: Optional[tuple[int, int]] = None, # render views' resolution, (h, w)
+ chunk_size: Optional[int] = 4,
+ trj_mode: Literal[
+ "original",
+ "smooth",
+ "interpolate",
+ "interpolate_smooth",
+ "wander",
+ "dolly_zoom",
+ "extend",
+ "wobble_inter",
+ ] = "extend",
+ color_mode: Literal["RGB+D", "RGB+ED"] = "RGB+ED",
+ vis_depth: Optional[Literal["hcat", "vcat"]] = "hcat",
+ enable_tqdm: Optional[bool] = True,
+ output_name: Optional[str] = None,
+ video_quality: Literal["low", "medium", "high"] = "high",
+) -> None:
+ gs_world = prediction.gaussians
+ # if target poses are not provided, render the (smooth/interpolate) input poses
+ if extrinsics is not None:
+ tgt_extrs = extrinsics
+ else:
+ tgt_extrs = torch.from_numpy(prediction.extrinsics).unsqueeze(0).to(gs_world.means)
+ if prediction.is_metric:
+ scale_factor = prediction.scale_factor
+ if scale_factor is not None:
+ tgt_extrs[:, :, :3, 3] /= scale_factor
+ tgt_intrs = (
+ intrinsics
+ if intrinsics is not None
+ else torch.from_numpy(prediction.intrinsics).unsqueeze(0).to(gs_world.means)
+ )
+ # if render resolution is not provided, render the input ones
+ if out_image_hw is not None:
+ H, W = out_image_hw
+ else:
+ H, W = prediction.depth.shape[-2:]
+ # if single views, render wander trj
+ if tgt_extrs.shape[1] <= 1:
+ trj_mode = "wander"
+ # trj_mode = "dolly_zoom"
+
+ color, depth = run_renderer_in_chunk_w_trj_mode(
+ gaussians=gs_world,
+ extrinsics=tgt_extrs,
+ intrinsics=tgt_intrs,
+ image_shape=(H, W),
+ chunk_size=chunk_size,
+ trj_mode=trj_mode,
+ use_sh=True,
+ color_mode=color_mode,
+ enable_tqdm=enable_tqdm,
+ )
+
+ # save as video
+ ffmpeg_params = [
+ "-crf",
+ VIDEO_QUALITY_MAP[video_quality]["crf"],
+ "-preset",
+ VIDEO_QUALITY_MAP[video_quality]["preset"],
+ "-pix_fmt",
+ "yuv420p",
+ ] # best compatibility
+
+ os.makedirs(os.path.join(export_dir, "gs_video"), exist_ok=True)
+ for idx in range(color.shape[0]):
+ video_i = color[idx]
+ if vis_depth is not None:
+ depth_i = vis_depth_map_tensor(depth[0])
+ cat_fn = hcat if vis_depth == "hcat" else vcat
+ video_i = torch.stack([cat_fn(c, d) for c, d in zip(video_i, depth_i)])
+ frames = list(
+ (video_i.clamp(0, 1) * 255).byte().permute(0, 2, 3, 1).cpu().numpy()
+ ) # T x H x W x C, uint8, numpy()
+
+ fps = 24
+ clip = mpy.ImageSequenceClip(frames, fps=fps)
+ output_name = f"{idx:04d}_{trj_mode}" if output_name is None else output_name
+ save_path = os.path.join(export_dir, f"gs_video/{output_name}.mp4")
+ # clip.write_videofile(save_path, codec="libx264", audio=False, bitrate="4000k")
+ clip.write_videofile(
+ save_path,
+ codec="libx264",
+ audio=False,
+ fps=fps,
+ ffmpeg_params=ffmpeg_params,
+ )
+ return
diff --git a/cameras/source/src/depth_anything_3/utils/export/npz.py b/cameras/source/src/depth_anything_3/utils/export/npz.py
new file mode 100644
index 0000000000000000000000000000000000000000..75df4f5194066c76a177f679e54721cb39739dd7
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/export/npz.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import numpy as np
+
+from depth_anything_3.specs import Prediction
+from depth_anything_3.utils.parallel_utils import async_call
+
+
+@async_call
+def export_to_npz(
+ prediction: Prediction,
+ export_dir: str,
+):
+ output_file = os.path.join(export_dir, "exports", "npz", "results.npz")
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
+
+ # Use prediction.processed_images, which is already processed image data
+ if prediction.processed_images is None:
+ raise ValueError("prediction.processed_images is required but not available")
+
+ image = prediction.processed_images # (N,H,W,3) uint8
+
+ # Build save dict with only non-None values
+ save_dict = {
+ "image": image,
+ "depth": np.round(prediction.depth, 6),
+ }
+
+ if prediction.conf is not None:
+ save_dict["conf"] = np.round(prediction.conf, 2)
+ if prediction.extrinsics is not None:
+ save_dict["extrinsics"] = prediction.extrinsics
+ if prediction.intrinsics is not None:
+ save_dict["intrinsics"] = prediction.intrinsics
+
+ # aux = {k: np.round(v, 4) for k, v in prediction.aux.items()}
+ np.savez_compressed(output_file, **save_dict)
+
+
+@async_call
+def export_to_mini_npz(
+ prediction: Prediction,
+ export_dir: str,
+):
+ output_file = os.path.join(export_dir, "exports", "mini_npz", "results.npz")
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
+
+ # Build save dict with only non-None values
+ save_dict = {
+ "depth": np.round(prediction.depth, 8),
+ }
+
+ if prediction.conf is not None:
+ save_dict["conf"] = np.round(prediction.conf, 2)
+ if prediction.extrinsics is not None:
+ save_dict["extrinsics"] = prediction.extrinsics
+ if prediction.intrinsics is not None:
+ save_dict["intrinsics"] = prediction.intrinsics
+
+ np.savez_compressed(output_file, **save_dict)
diff --git a/cameras/source/src/depth_anything_3/utils/export/utils.py b/cameras/source/src/depth_anything_3/utils/export/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..81f45fb563ce595bf547bebe829c9b83eb175f1c
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/export/utils.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import torch
+
+
+def _denorm_and_to_uint8(image_tensor: torch.Tensor) -> np.ndarray:
+ """Denormalize to [0,255] and output (N, H, W, 3) uint8."""
+ resnet_mean = torch.tensor(
+ [0.485, 0.456, 0.406], dtype=image_tensor.dtype, device=image_tensor.device
+ )
+ resnet_std = torch.tensor(
+ [0.229, 0.224, 0.225], dtype=image_tensor.dtype, device=image_tensor.device
+ )
+ img = image_tensor * resnet_std[None, :, None, None] + resnet_mean[None, :, None, None]
+ img = torch.clamp(img, 0.0, 1.0)
+ img = (img.permute(0, 2, 3, 1).cpu().numpy() * 255.0).round().astype(np.uint8) # (N,H,W,3)
+ return img
diff --git a/cameras/source/src/depth_anything_3/utils/geometry.py b/cameras/source/src/depth_anything_3/utils/geometry.py
new file mode 100644
index 0000000000000000000000000000000000000000..41a42190d637ef25db3599cc90f9a4f40be3b8d7
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/geometry.py
@@ -0,0 +1,498 @@
+# flake8: noqa: F722
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from types import SimpleNamespace
+from typing import Optional
+import numpy as np
+import torch
+import torch.nn.functional as F
+from einops import einsum
+
+
+def as_homogeneous(ext):
+ """
+ Accept (..., 3,4) or (..., 4,4) extrinsics, return (...,4,4) homogeneous matrix.
+ Supports torch.Tensor or np.ndarray.
+ """
+ if isinstance(ext, torch.Tensor):
+ # If already in homogeneous form
+ if ext.shape[-2:] == (4, 4):
+ return ext
+ elif ext.shape[-2:] == (3, 4):
+ # Create a new homogeneous matrix
+ ones = torch.zeros_like(ext[..., :1, :4])
+ ones[..., 0, 3] = 1.0
+ return torch.cat([ext, ones], dim=-2)
+ else:
+ raise ValueError(f"Invalid shape for torch.Tensor: {ext.shape}")
+
+ elif isinstance(ext, np.ndarray):
+ if ext.shape[-2:] == (4, 4):
+ return ext
+ elif ext.shape[-2:] == (3, 4):
+ ones = np.zeros_like(ext[..., :1, :4])
+ ones[..., 0, 3] = 1.0
+ return np.concatenate([ext, ones], axis=-2)
+ else:
+ raise ValueError(f"Invalid shape for np.ndarray: {ext.shape}")
+
+ else:
+ raise TypeError("Input must be a torch.Tensor or np.ndarray.")
+
+
+@torch.jit.script
+def affine_inverse(A: torch.Tensor):
+ R = A[..., :3, :3] # ..., 3, 3
+ T = A[..., :3, 3:] # ..., 3, 1
+ P = A[..., 3:, :] # ..., 1, 4
+ return torch.cat([torch.cat([R.mT, -R.mT @ T], dim=-1), P], dim=-2)
+
+
+def transpose_last_two_axes(arr):
+ """
+ for np < 2
+ """
+ if arr.ndim < 2:
+ return arr
+ axes = list(range(arr.ndim))
+ # swap the last two
+ axes[-2], axes[-1] = axes[-1], axes[-2]
+ return arr.transpose(axes)
+
+
+def affine_inverse_np(A: np.ndarray):
+ R = A[..., :3, :3]
+ T = A[..., :3, 3:]
+ P = A[..., 3:, :]
+ return np.concatenate(
+ [
+ np.concatenate([transpose_last_two_axes(R), -transpose_last_two_axes(R) @ T], axis=-1),
+ P,
+ ],
+ axis=-2,
+ )
+
+
+def quat_to_mat(quaternions: torch.Tensor) -> torch.Tensor:
+ """
+ Quaternion Order: XYZW or say ijkr, scalar-last
+
+ Convert rotations given as quaternions to rotation matrices.
+ Args:
+ quaternions: quaternions with real part last,
+ as tensor of shape (..., 4).
+
+ Returns:
+ Rotation matrices as tensor of shape (..., 3, 3).
+ """
+ i, j, k, r = torch.unbind(quaternions, -1)
+ # pyre-fixme[58]: `/` is not supported for operand types `float` and `Tensor`.
+ two_s = 2.0 / (quaternions * quaternions).sum(-1)
+
+ o = torch.stack(
+ (
+ 1 - two_s * (j * j + k * k),
+ two_s * (i * j - k * r),
+ two_s * (i * k + j * r),
+ two_s * (i * j + k * r),
+ 1 - two_s * (i * i + k * k),
+ two_s * (j * k - i * r),
+ two_s * (i * k - j * r),
+ two_s * (j * k + i * r),
+ 1 - two_s * (i * i + j * j),
+ ),
+ -1,
+ )
+ return o.reshape(quaternions.shape[:-1] + (3, 3))
+
+
+def mat_to_quat(matrix: torch.Tensor) -> torch.Tensor:
+ """
+ Convert rotations given as rotation matrices to quaternions.
+
+ Args:
+ matrix: Rotation matrices as tensor of shape (..., 3, 3).
+
+ Returns:
+ quaternions with real part last, as tensor of shape (..., 4).
+ Quaternion Order: XYZW or say ijkr, scalar-last
+ """
+ if matrix.size(-1) != 3 or matrix.size(-2) != 3:
+ raise ValueError(f"Invalid rotation matrix shape {matrix.shape}.")
+
+ batch_dim = matrix.shape[:-2]
+ m00, m01, m02, m10, m11, m12, m20, m21, m22 = torch.unbind(
+ matrix.reshape(batch_dim + (9,)), dim=-1
+ )
+
+ q_abs = _sqrt_positive_part(
+ torch.stack(
+ [
+ 1.0 + m00 + m11 + m22,
+ 1.0 + m00 - m11 - m22,
+ 1.0 - m00 + m11 - m22,
+ 1.0 - m00 - m11 + m22,
+ ],
+ dim=-1,
+ )
+ )
+
+ # we produce the desired quaternion multiplied by each of r, i, j, k
+ quat_by_rijk = torch.stack(
+ [
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
+ # `int`.
+ torch.stack([q_abs[..., 0] ** 2, m21 - m12, m02 - m20, m10 - m01], dim=-1),
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
+ # `int`.
+ torch.stack([m21 - m12, q_abs[..., 1] ** 2, m10 + m01, m02 + m20], dim=-1),
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
+ # `int`.
+ torch.stack([m02 - m20, m10 + m01, q_abs[..., 2] ** 2, m12 + m21], dim=-1),
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
+ # `int`.
+ torch.stack([m10 - m01, m20 + m02, m21 + m12, q_abs[..., 3] ** 2], dim=-1),
+ ],
+ dim=-2,
+ )
+
+ # We floor here at 0.1 but the exact level is not important; if q_abs is small,
+ # the candidate won't be picked.
+ flr = torch.tensor(0.1).to(dtype=q_abs.dtype, device=q_abs.device)
+ quat_candidates = quat_by_rijk / (2.0 * q_abs[..., None].max(flr))
+
+ # if not for numerical problems, quat_candidates[i] should be same (up to a sign),
+ # forall i; we pick the best-conditioned one (with the largest denominator)
+ out = quat_candidates[F.one_hot(q_abs.argmax(dim=-1), num_classes=4) > 0.5, :].reshape(
+ batch_dim + (4,)
+ )
+
+ # Convert from rijk to ijkr
+ out = out[..., [1, 2, 3, 0]]
+
+ out = standardize_quaternion(out)
+
+ return out
+
+
+def _sqrt_positive_part(x: torch.Tensor) -> torch.Tensor:
+ """
+ Returns torch.sqrt(torch.max(0, x))
+ but with a zero subgradient where x is 0.
+ """
+ ret = torch.zeros_like(x)
+ positive_mask = x > 0
+ if torch.is_grad_enabled():
+ ret[positive_mask] = torch.sqrt(x[positive_mask])
+ else:
+ ret = torch.where(positive_mask, torch.sqrt(x), ret)
+ return ret
+
+
+def standardize_quaternion(quaternions: torch.Tensor) -> torch.Tensor:
+ """
+ Convert a unit quaternion to a standard form: one in which the real
+ part is non negative.
+
+ Args:
+ quaternions: Quaternions with real part last,
+ as tensor of shape (..., 4).
+
+ Returns:
+ Standardized quaternions as tensor of shape (..., 4).
+ """
+ return torch.where(quaternions[..., 3:4] < 0, -quaternions, quaternions)
+
+
+def sample_image_grid(
+ shape: tuple[int, ...],
+ device: torch.device = torch.device("cpu"),
+) -> tuple[
+ torch.Tensor, # float coordinates (xy indexing), "*shape dim"
+ torch.Tensor, # integer indices (ij indexing), "*shape dim"
+]:
+ """Get normalized (range 0 to 1) coordinates and integer indices for an image."""
+
+ # Each entry is a pixel-wise integer coordinate. In the 2D case, each entry is a
+ # (row, col) coordinate.
+ indices = [torch.arange(length, device=device) for length in shape]
+ stacked_indices = torch.stack(torch.meshgrid(*indices, indexing="ij"), dim=-1)
+
+ # Each entry is a floating-point coordinate in the range (0, 1). In the 2D case,
+ # each entry is an (x, y) coordinate.
+ coordinates = [(idx + 0.5) / length for idx, length in zip(indices, shape)]
+ coordinates = reversed(coordinates)
+ coordinates = torch.stack(torch.meshgrid(*coordinates, indexing="xy"), dim=-1)
+
+ return coordinates, stacked_indices
+
+
+def homogenize_points(points: torch.Tensor) -> torch.Tensor: # "*batch dim" # "*batch dim+1"
+ """Convert batched points (xyz) to (xyz1)."""
+ return torch.cat([points, torch.ones_like(points[..., :1])], dim=-1)
+
+
+def homogenize_vectors(vectors: torch.Tensor) -> torch.Tensor: # "*batch dim" # "*batch dim+1"
+ """Convert batched vectors (xyz) to (xyz0)."""
+ return torch.cat([vectors, torch.zeros_like(vectors[..., :1])], dim=-1)
+
+
+def transform_rigid(
+ homogeneous_coordinates: torch.Tensor, # "*#batch dim"
+ transformation: torch.Tensor, # "*#batch dim dim"
+) -> torch.Tensor: # "*batch dim"
+ """Apply a rigid-body transformation to points or vectors."""
+ return einsum(
+ transformation,
+ homogeneous_coordinates.to(transformation.dtype),
+ "... i j, ... j -> ... i",
+ )
+
+
+def transform_cam2world(
+ homogeneous_coordinates: torch.Tensor, # "*#batch dim"
+ extrinsics: torch.Tensor, # "*#batch dim dim"
+) -> torch.Tensor: # "*batch dim"
+ """Transform points from 3D camera coordinates to 3D world coordinates."""
+ return transform_rigid(homogeneous_coordinates, extrinsics)
+
+
+def unproject(
+ coordinates: torch.Tensor, # "*#batch dim"
+ z: torch.Tensor, # "*#batch"
+ intrinsics: torch.Tensor, # "*#batch dim+1 dim+1"
+) -> torch.Tensor: # "*batch dim+1"
+ """Unproject 2D camera coordinates with the given Z values."""
+
+ # Apply the inverse intrinsics to the coordinates.
+ coordinates = homogenize_points(coordinates)
+ ray_directions = einsum(
+ intrinsics.float().inverse().to(intrinsics),
+ coordinates.to(intrinsics.dtype),
+ "... i j, ... j -> ... i",
+ )
+
+ # Apply the supplied depth values.
+ return ray_directions * z[..., None]
+
+
+def get_world_rays(
+ coordinates: torch.Tensor, # "*#batch dim"
+ extrinsics: torch.Tensor, # "*#batch dim+2 dim+2"
+ intrinsics: torch.Tensor, # "*#batch dim+1 dim+1"
+) -> tuple[
+ torch.Tensor, # origins, "*batch dim+1"
+ torch.Tensor, # directions, "*batch dim+1"
+]:
+ # Get camera-space ray directions.
+ directions = unproject(
+ coordinates,
+ torch.ones_like(coordinates[..., 0]),
+ intrinsics,
+ )
+ directions = directions / directions.norm(dim=-1, keepdim=True)
+
+ # Transform ray directions to world coordinates.
+ directions = homogenize_vectors(directions)
+ directions = transform_cam2world(directions, extrinsics)[..., :-1]
+
+ # Tile the ray origins to have the same shape as the ray directions.
+ origins = extrinsics[..., :-1, -1].broadcast_to(directions.shape)
+
+ return origins, directions
+
+
+def get_fov(intrinsics: torch.Tensor) -> torch.Tensor: # "batch 3 3" -> "batch 2"
+ intrinsics_inv = intrinsics.float().inverse().to(intrinsics)
+
+ def process_vector(vector):
+ vector = torch.tensor(vector, dtype=intrinsics.dtype, device=intrinsics.device)
+ vector = einsum(intrinsics_inv, vector, "b i j, j -> b i")
+ return vector / vector.norm(dim=-1, keepdim=True)
+
+ left = process_vector([0, 0.5, 1])
+ right = process_vector([1, 0.5, 1])
+ top = process_vector([0.5, 0, 1])
+ bottom = process_vector([0.5, 1, 1])
+ fov_x = (left * right).sum(dim=-1).acos()
+ fov_y = (top * bottom).sum(dim=-1).acos()
+ return torch.stack((fov_x, fov_y), dim=-1)
+
+
+def map_pdf_to_opacity(
+ pdf: torch.Tensor, # " *batch"
+ global_step: int = 0,
+ opacity_mapping: Optional[dict] = None,
+) -> torch.Tensor: # " *batch"
+ # https://www.desmos.com/calculator/opvwti3ba9
+
+ # Figure out the exponent.
+ if opacity_mapping is not None:
+ cfg = SimpleNamespace(**opacity_mapping)
+ x = cfg.initial + min(global_step / cfg.warm_up, 1) * (cfg.final - cfg.initial)
+ else:
+ x = 0.0
+ exponent = 2**x
+
+ # Map the probability density to an opacity.
+ return 0.5 * (1 - (1 - pdf) ** exponent + pdf ** (1 / exponent))
+
+def normalize_homogenous_points(points):
+ """Normalize the point vectors"""
+ return points / points[..., -1:]
+
+def inverse_intrinsic_matrix(ixts):
+ """ """
+ return torch.inverse(ixts)
+
+def pixel_space_to_camera_space(pixel_space_points, depth, intrinsics):
+ """
+ Convert pixel space points to camera space points.
+
+ Args:
+ pixel_space_points (torch.Tensor): Pixel space points with shape (h, w, 2)
+ depth (torch.Tensor): Depth map with shape (b, v, h, w, 1)
+ intrinsics (torch.Tensor): Camera intrinsics with shape (b, v, 3, 3)
+
+ Returns:
+ torch.Tensor: Camera space points with shape (b, v, h, w, 3).
+ """
+ pixel_space_points = homogenize_points(pixel_space_points)
+ # camera_space_points = torch.einsum(
+ # "b v i j , h w j -> b v h w i", intrinsics.inverse(), pixel_space_points
+ # )
+ camera_space_points = torch.einsum(
+ "b v i j , h w j -> b v h w i", inverse_intrinsic_matrix(intrinsics), pixel_space_points
+ )
+ camera_space_points = camera_space_points * depth
+ return camera_space_points
+
+
+def camera_space_to_world_space(camera_space_points, c2w):
+ """
+ Convert camera space points to world space points.
+
+ Args:
+ camera_space_points (torch.Tensor): Camera space points with shape (b, v, h, w, 3)
+ c2w (torch.Tensor): Camera to world extrinsics matrix with shape (b, v, 4, 4)
+
+ Returns:
+ torch.Tensor: World space points with shape (b, v, h, w, 3).
+ """
+ camera_space_points = homogenize_points(camera_space_points)
+ world_space_points = torch.einsum("b v i j , b v h w j -> b v h w i", c2w, camera_space_points)
+ return world_space_points[..., :3]
+
+
+def camera_space_to_pixel_space(camera_space_points, intrinsics):
+ """
+ Convert camera space points to pixel space points.
+
+ Args:
+ camera_space_points (torch.Tensor): Camera space points with shape (b, v1, v2, h, w, 3)
+ c2w (torch.Tensor): Camera to world extrinsics matrix with shape (b, v2, 3, 3)
+
+ Returns:
+ torch.Tensor: World space points with shape (b, v1, v2, h, w, 2).
+ """
+ camera_space_points = normalize_homogenous_points(camera_space_points)
+ pixel_space_points = torch.einsum(
+ "b u i j , b v u h w j -> b v u h w i", intrinsics, camera_space_points
+ )
+ return pixel_space_points[..., :2]
+
+
+def world_space_to_camera_space(world_space_points, c2w):
+ """
+ Convert world space points to pixel space points.
+
+ Args:
+ world_space_points (torch.Tensor): World space points with shape (b, v1, h, w, 3)
+ c2w (torch.Tensor): Camera to world extrinsics matrix with shape (b, v2, 4, 4)
+
+ Returns:
+ torch.Tensor: Camera space points with shape (b, v1, v2, h, w, 3).
+ """
+ world_space_points = homogenize_points(world_space_points)
+ camera_space_points = torch.einsum(
+ "b u i j , b v h w j -> b v u h w i", c2w.inverse(), world_space_points
+ )
+ return camera_space_points[..., :3]
+
+
+def unproject_depth(
+ depth, intrinsics, c2w=None, ixt_normalized=False, num_patches_x=None, num_patches_y=None
+):
+ """
+ Turn the depth map into a 3D point cloud in world space
+
+ Args:
+ depth: (b, v, h, w, 1)
+ intrinsics: (b, v, 3, 3)
+ c2w: (b, v, 4, 4)
+
+ Returns:
+ torch.Tensor: World space points with shape (b, v, h, w, 3).
+ """
+ if c2w is None:
+ c2w = torch.eye(4, device=depth.device, dtype=depth.dtype)
+ c2w = c2w[None, None].repeat(depth.shape[0], depth.shape[1], 1, 1)
+
+ if not ixt_normalized:
+ # Compute indices of pixels
+ h, w = depth.shape[-3], depth.shape[-2]
+ x_grid, y_grid = torch.meshgrid(
+ torch.arange(w, device=depth.device, dtype=depth.dtype),
+ torch.arange(h, device=depth.device, dtype=depth.dtype),
+ indexing="xy",
+ ) # (h, w), (h, w)
+ else:
+ # ixt_normalized: h=w=2.0. cx, cy, fx, fy are normalized according to h=w=2.0
+ assert num_patches_x is not None and num_patches_y is not None
+ dx = 1 / num_patches_x
+ dy = 1 / num_patches_y
+ max_y = 1 - dy
+ min_y = -max_y
+ max_x = 1 - dx
+ min_x = -max_x
+
+ grid_shift = 1.0
+ y_grid, x_grid = torch.meshgrid(
+ torch.linspace(
+ min_y + grid_shift,
+ max_y + grid_shift,
+ num_patches_y,
+ dtype=torch.float32,
+ device=depth.device,
+ ),
+ torch.linspace(
+ min_x + grid_shift,
+ max_x + grid_shift,
+ num_patches_x,
+ dtype=torch.float32,
+ device=depth.device,
+ ),
+ indexing="ij",
+ )
+
+ # Compute coordinates of pixels in camera space
+ pixel_space_points = torch.stack((x_grid, y_grid), dim=-1) # (..., h, w, 2)
+ camera_points = pixel_space_to_camera_space(
+ pixel_space_points, depth, intrinsics
+ ) # (..., h, w, 3)
+
+ # Convert points to world space
+ world_points = camera_space_to_world_space(camera_points, c2w) # (..., h, w, 3)
+
+ return world_points
\ No newline at end of file
diff --git a/cameras/source/src/depth_anything_3/utils/gsply_helpers.py b/cameras/source/src/depth_anything_3/utils/gsply_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..5733009e4e91ad80ab59179c80e2df8a0430ab5f
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/gsply_helpers.py
@@ -0,0 +1,173 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from pathlib import Path
+from typing import Optional
+import numpy as np
+import torch
+from einops import rearrange, repeat
+from plyfile import PlyData, PlyElement
+from torch import Tensor
+
+from depth_anything_3.specs import Gaussians
+
+
+def construct_list_of_attributes(num_rest: int) -> list[str]:
+ attributes = ["x", "y", "z", "nx", "ny", "nz"]
+ for i in range(3):
+ attributes.append(f"f_dc_{i}")
+ for i in range(num_rest):
+ attributes.append(f"f_rest_{i}")
+ attributes.append("opacity")
+ for i in range(3):
+ attributes.append(f"scale_{i}")
+ for i in range(4):
+ attributes.append(f"rot_{i}")
+ return attributes
+
+
+def export_ply(
+ means: Tensor, # "gaussian 3"
+ scales: Tensor, # "gaussian 3"
+ rotations: Tensor, # "gaussian 4"
+ harmonics: Tensor, # "gaussian 3 d_sh"
+ opacities: Tensor, # "gaussian"
+ path: Path,
+ shift_and_scale: bool = False,
+ save_sh_dc_only: bool = True,
+ match_3dgs_mcmc_dev: Optional[bool] = False,
+):
+ if shift_and_scale:
+ # Shift the scene so that the median Gaussian is at the origin.
+ means = means - means.median(dim=0).values
+
+ # Rescale the scene so that most Gaussians are within range [-1, 1].
+ scale_factor = means.abs().quantile(0.95, dim=0).max()
+ means = means / scale_factor
+ scales = scales / scale_factor
+
+ rotations = rotations.detach().cpu().numpy()
+
+ # Since current model use SH_degree = 4,
+ # which require large memory to store, we can only save the DC band to save memory.
+ f_dc = harmonics[..., 0]
+ f_rest = harmonics[..., 1:].flatten(start_dim=1)
+
+ if match_3dgs_mcmc_dev:
+ sh_degree = 3
+ n_rest = 3 * (sh_degree + 1) ** 2 - 3
+ f_rest = repeat(
+ torch.zeros_like(harmonics[..., :1]), "... i -> ... (n i)", n=(n_rest // 3)
+ ).flatten(start_dim=1)
+ dtype_full = [
+ (attribute, "f4")
+ for attribute in construct_list_of_attributes(num_rest=n_rest)
+ if attribute not in ("nx", "ny", "nz")
+ ]
+ else:
+ dtype_full = [
+ (attribute, "f4")
+ for attribute in construct_list_of_attributes(
+ 0 if save_sh_dc_only else f_rest.shape[1]
+ )
+ ]
+ elements = np.empty(means.shape[0], dtype=dtype_full)
+ attributes = [
+ means.detach().cpu().numpy(),
+ torch.zeros_like(means).detach().cpu().numpy(),
+ f_dc.detach().cpu().contiguous().numpy(),
+ f_rest.detach().cpu().contiguous().numpy(),
+ opacities[..., None].detach().cpu().numpy(),
+ scales.log().detach().cpu().numpy(),
+ rotations,
+ ]
+ if match_3dgs_mcmc_dev:
+ attributes.pop(1) # dummy normal is not needed
+ elif save_sh_dc_only:
+ attributes.pop(3) # remove f_rest from attributes
+
+ attributes = np.concatenate(attributes, axis=1)
+ elements[:] = list(map(tuple, attributes))
+ path.parent.mkdir(exist_ok=True, parents=True)
+ PlyData([PlyElement.describe(elements, "vertex")]).write(path)
+
+
+def inverse_sigmoid(x):
+ return torch.log(x / (1 - x))
+
+
+def save_gaussian_ply(
+ gaussians: Gaussians,
+ save_path: str,
+ ctx_depth: torch.Tensor, # depth of input views; for getting shape and filtering, "v h w 1"
+ shift_and_scale: bool = False,
+ save_sh_dc_only: bool = True,
+ gs_views_interval: int = 1,
+ inv_opacity: Optional[bool] = True,
+ prune_by_depth_percent: Optional[float] = 1.0,
+ prune_border_gs: Optional[bool] = True,
+ match_3dgs_mcmc_dev: Optional[bool] = False,
+):
+ b = gaussians.means.shape[0]
+ assert b == 1, "must set batch_size=1 when exporting 3D gaussians"
+ src_v, out_h, out_w, _ = ctx_depth.shape
+
+ # extract gs params
+ world_means = gaussians.means
+ world_shs = gaussians.harmonics
+ world_rotations = gaussians.rotations
+ gs_scales = gaussians.scales
+ gs_opacities = inverse_sigmoid(gaussians.opacities) if inv_opacity else gaussians.opacities
+
+ # Create a mask to filter the Gaussians.
+
+ # TODO: prune the sky region here
+
+ # throw away Gaussians at the borders, since they're generally of lower quality.
+ if prune_border_gs:
+ mask = torch.zeros_like(ctx_depth, dtype=torch.bool)
+ gstrim_h = int(8 / 256 * out_h)
+ gstrim_w = int(8 / 256 * out_w)
+ mask[:, gstrim_h:-gstrim_h, gstrim_w:-gstrim_w, :] = 1
+ else:
+ mask = torch.ones_like(ctx_depth, dtype=torch.bool)
+
+ # trim the far away point based on depth;
+ if prune_by_depth_percent is not None and prune_by_depth_percent < 1:
+ in_depths = ctx_depth
+ d_percentile = torch.quantile(
+ in_depths.view(in_depths.shape[0], -1), q=prune_by_depth_percent, dim=1
+ ).view(-1, 1, 1)
+ d_mask = (in_depths[..., 0] <= d_percentile).unsqueeze(-1)
+ mask = mask & d_mask
+ mask = mask.squeeze(-1) # v h w
+
+ # helper fn, must place after mask
+ def trim_select_reshape(element):
+ selected_element = rearrange(
+ element[0], "(v h w) ... -> v h w ...", v=src_v, h=out_h, w=out_w
+ )
+ selected_element = selected_element[::gs_views_interval][mask[::gs_views_interval]]
+ return selected_element
+
+ export_ply(
+ means=trim_select_reshape(world_means),
+ scales=trim_select_reshape(gs_scales),
+ rotations=trim_select_reshape(world_rotations),
+ harmonics=trim_select_reshape(world_shs),
+ opacities=trim_select_reshape(gs_opacities),
+ path=Path(save_path),
+ shift_and_scale=shift_and_scale,
+ save_sh_dc_only=save_sh_dc_only,
+ match_3dgs_mcmc_dev=match_3dgs_mcmc_dev,
+ )
diff --git a/cameras/source/src/depth_anything_3/utils/io/input_processor.py b/cameras/source/src/depth_anything_3/utils/io/input_processor.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa60194123c22c84a0cdeb577cbf10e2704278bb
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/io/input_processor.py
@@ -0,0 +1,501 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Input processor for Depth Anything 3 (parallelized).
+
+This version removes the square center-crop step for "*crop" methods (same as your note).
+In addition, it parallelizes per-image preprocessing using the provided `parallel_execution`.
+"""
+
+from __future__ import annotations
+
+from typing import Sequence
+import cv2
+import numpy as np
+import torch
+import torchvision.transforms as T
+from PIL import Image
+
+from depth_anything_3.utils.logger import logger
+from depth_anything_3.utils.parallel_utils import parallel_execution
+
+
+class InputProcessor:
+ """Prepares a batch of images for model inference.
+ This processor converts a list of image file paths into a single, model-ready
+ tensor. The processing pipeline is executed in parallel across multiple workers
+ for efficiency.
+
+ Pipeline:
+ 1) Load image and convert to RGB
+ 2) Boundary resize (upper/lower bound, preserving aspect ratio)
+ 3) Enforce divisibility by PATCH_SIZE:
+ - "*resize" methods: each dimension is rounded to nearest multiple
+ (may up/downscale a few px)
+ - "*crop" methods: each dimension is floored to nearest multiple via center crop
+ 4) Convert to tensor and apply ImageNet normalization
+ 5) Stack into (1, N, 3, H, W)
+
+ Parallelization:
+ - Each image is processed independently in a worker.
+ - Order of outputs matches the input order.
+ """
+
+ NORMALIZE = T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
+ PATCH_SIZE = 14
+
+ def __init__(self):
+ pass
+
+ # -----------------------------
+ # Public API
+ # -----------------------------
+ def __call__(
+ self,
+ image: list[np.ndarray | Image.Image | str],
+ extrinsics: np.ndarray | None = None,
+ intrinsics: np.ndarray | None = None,
+ process_res: int = 504,
+ process_res_method: str = "upper_bound_resize",
+ *,
+ num_workers: int = 8,
+ print_progress: bool = False,
+ sequential: bool | None = None,
+ desc: str | None = "Preprocess",
+ ) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor | None]:
+ """
+ Returns:
+ (tensor, extrinsics_list, intrinsics_list)
+ tensor shape: (1, N, 3, H, W)
+ """
+ sequential = self._resolve_sequential(sequential, num_workers)
+ exts_list, ixts_list = self._validate_and_pack_meta(image, extrinsics, intrinsics)
+
+ results = self._run_parallel(
+ image=image,
+ exts_list=exts_list,
+ ixts_list=ixts_list,
+ process_res=process_res,
+ process_res_method=process_res_method,
+ num_workers=num_workers,
+ print_progress=print_progress,
+ sequential=sequential,
+ desc=desc,
+ )
+
+ proc_imgs, out_sizes, out_ixts, out_exts = self._unpack_results(results)
+ proc_imgs, out_sizes, out_ixts = self._unify_batch_shapes(proc_imgs, out_sizes, out_ixts)
+
+ batch_tensor = self._stack_batch(proc_imgs)
+ out_exts = (
+ torch.from_numpy(np.asarray(out_exts)).float()
+ if out_exts is not None and out_exts[0] is not None
+ else None
+ )
+ out_ixts = (
+ torch.from_numpy(np.asarray(out_ixts)).float()
+ if out_ixts is not None and out_ixts[0] is not None
+ else None
+ )
+ return (batch_tensor, out_exts, out_ixts)
+
+ # -----------------------------
+ # __call__ helpers
+ # -----------------------------
+ def _resolve_sequential(self, sequential: bool | None, num_workers: int) -> bool:
+ return (num_workers <= 1) if sequential is None else sequential
+
+ def _validate_and_pack_meta(
+ self,
+ images: list[np.ndarray | Image.Image | str],
+ extrinsics: np.ndarray | None,
+ intrinsics: np.ndarray | None,
+ ) -> tuple[list[np.ndarray | None] | None, list[np.ndarray | None] | None]:
+ if extrinsics is not None and len(extrinsics) != len(images):
+ raise ValueError("Length of extrinsics must match images when provided.")
+ if intrinsics is not None and len(intrinsics) != len(images):
+ raise ValueError("Length of intrinsics must match images when provided.")
+ exts_list = [e for e in extrinsics] if extrinsics is not None else None
+ ixts_list = [k for k in intrinsics] if intrinsics is not None else None
+ return exts_list, ixts_list
+
+ def _run_parallel(
+ self,
+ *,
+ image: list[np.ndarray | Image.Image | str],
+ exts_list: list[np.ndarray | None] | None,
+ ixts_list: list[np.ndarray | None] | None,
+ process_res: int,
+ process_res_method: str,
+ num_workers: int,
+ print_progress: bool,
+ sequential: bool,
+ desc: str | None,
+ ):
+ results = parallel_execution(
+ image,
+ exts_list,
+ ixts_list,
+ action=self._process_one, # (img, extrinsic, intrinsic, ...)
+ num_processes=num_workers,
+ print_progress=print_progress,
+ sequential=sequential,
+ desc=desc,
+ process_res=process_res,
+ process_res_method=process_res_method,
+ )
+ if not results:
+ raise RuntimeError(
+ "No preprocessing results returned. Check inputs and parallel_execution."
+ )
+ return results
+
+ def _unpack_results(self, results):
+ """
+ results: List[Tuple[torch.Tensor, Tuple[H, W], Optional[np.ndarray], Optional[np.ndarray]]]
+ -> processed_images, out_sizes, out_intrinsics, out_extrinsics
+ """
+ try:
+ processed_images, out_sizes, out_intrinsics, out_extrinsics = zip(*results)
+ except Exception as e:
+ raise RuntimeError(
+ "Unexpected results structure from parallel_execution: "
+ f"{type(results)} / sample: {results[0]}"
+ ) from e
+
+ return list(processed_images), list(out_sizes), list(out_intrinsics), list(out_extrinsics)
+
+ def _unify_batch_shapes(
+ self,
+ processed_images: list[torch.Tensor],
+ out_sizes: list[tuple[int, int]],
+ out_intrinsics: list[np.ndarray | None],
+ ) -> tuple[list[torch.Tensor], list[tuple[int, int]], list[np.ndarray | None]]:
+ """Center-crop all tensors to the smallest H, W; adjust intrinsics' cx, cy accordingly."""
+ if len(set(out_sizes)) <= 1:
+ return processed_images, out_sizes, out_intrinsics
+
+ min_h = min(h for h, _ in out_sizes)
+ min_w = min(w for _, w in out_sizes)
+ logger.warn(
+ f"Images in batch have different sizes {out_sizes}; "
+ f"center-cropping all to smallest ({min_h},{min_w})"
+ )
+
+ center_crop = T.CenterCrop((min_h, min_w))
+ new_imgs, new_sizes, new_ixts = [], [], []
+ for img_t, (H, W), K in zip(processed_images, out_sizes, out_intrinsics):
+ crop_top = max(0, (H - min_h) // 2)
+ crop_left = max(0, (W - min_w) // 2)
+ new_imgs.append(center_crop(img_t))
+ new_sizes.append((min_h, min_w))
+ if K is None:
+ new_ixts.append(None)
+ else:
+ K_adj = K.copy()
+ K_adj[0, 2] -= crop_left
+ K_adj[1, 2] -= crop_top
+ new_ixts.append(K_adj)
+ return new_imgs, new_sizes, new_ixts
+
+ def _stack_batch(self, processed_images: list[torch.Tensor]) -> torch.Tensor:
+ return torch.stack(processed_images)
+
+ # -----------------------------
+ # Per-item worker
+ # -----------------------------
+ def _process_one(
+ self,
+ img: np.ndarray | Image.Image | str,
+ extrinsic: np.ndarray | None = None,
+ intrinsic: np.ndarray | None = None,
+ *,
+ process_res: int,
+ process_res_method: str,
+ ) -> tuple[torch.Tensor, tuple[int, int], np.ndarray | None, np.ndarray | None]:
+ # Load & remember original size
+ pil_img = self._load_image(img)
+ orig_w, orig_h = pil_img.size
+
+ # Boundary resize
+ pil_img = self._resize_image(pil_img, process_res, process_res_method)
+ w, h = pil_img.size
+ intrinsic = self._resize_ixt(intrinsic, orig_w, orig_h, w, h)
+
+ # Enforce divisibility by PATCH_SIZE
+ if process_res_method.endswith("resize"):
+ pil_img = self._make_divisible_by_resize(pil_img, self.PATCH_SIZE)
+ new_w, new_h = pil_img.size
+ intrinsic = self._resize_ixt(intrinsic, w, h, new_w, new_h)
+ w, h = new_w, new_h
+ elif process_res_method.endswith("crop"):
+ pil_img = self._make_divisible_by_crop(pil_img, self.PATCH_SIZE)
+ new_w, new_h = pil_img.size
+ intrinsic = self._crop_ixt(intrinsic, w, h, new_w, new_h)
+ w, h = new_w, new_h
+ else:
+ raise ValueError(f"Unsupported process_res_method: {process_res_method}")
+
+ # Convert to tensor & normalize
+ img_tensor = self._normalize_image(pil_img)
+ _, H, W = img_tensor.shape
+ assert (W, H) == (w, h), "Tensor size mismatch with PIL image size after processing."
+
+ # Return: (img_tensor, (H, W), intrinsic, extrinsic)
+ return img_tensor, (H, W), intrinsic, extrinsic
+
+ # -----------------------------
+ # Intrinsics transforms
+ # -----------------------------
+ def _resize_ixt(
+ self,
+ intrinsic: np.ndarray | None,
+ orig_w: int,
+ orig_h: int,
+ w: int,
+ h: int,
+ ) -> np.ndarray | None:
+ if intrinsic is None:
+ return None
+ K = intrinsic.copy()
+ # scale fx, cx by w ratio; fy, cy by h ratio
+ K[:1] *= w / float(orig_w)
+ K[1:2] *= h / float(orig_h)
+ return K
+
+ def _crop_ixt(
+ self,
+ intrinsic: np.ndarray | None,
+ orig_w: int,
+ orig_h: int,
+ w: int,
+ h: int,
+ ) -> np.ndarray | None:
+ if intrinsic is None:
+ return None
+ K = intrinsic.copy()
+ crop_h = (orig_h - h) // 2
+ crop_w = (orig_w - w) // 2
+ K[0, 2] -= crop_w
+ K[1, 2] -= crop_h
+ return K
+
+ # -----------------------------
+ # I/O & normalization
+ # -----------------------------
+ def _load_image(self, img: np.ndarray | Image.Image | str) -> Image.Image:
+ if isinstance(img, str):
+ return Image.open(img).convert("RGB")
+ elif isinstance(img, np.ndarray):
+ # Assume HxWxC uint8/RGB
+ return Image.fromarray(img).convert("RGB")
+ elif isinstance(img, Image.Image):
+ return img.convert("RGB")
+ else:
+ raise ValueError(f"Unsupported image type: {type(img)}")
+
+ def _normalize_image(self, img: Image.Image) -> torch.Tensor:
+ img_tensor = T.ToTensor()(img)
+ return self.NORMALIZE(img_tensor)
+
+ # -----------------------------
+ # Boundary resizing
+ # -----------------------------
+ def _resize_image(self, img: Image.Image, target_size: int, method: str) -> Image.Image:
+ if method in ("upper_bound_resize", "upper_bound_crop"):
+ return self._resize_longest_side(img, target_size)
+ elif method in ("lower_bound_resize", "lower_bound_crop"):
+ return self._resize_shortest_side(img, target_size)
+ else:
+ raise ValueError(f"Unsupported resize method: {method}")
+
+ def _resize_longest_side(self, img: Image.Image, target_size: int) -> Image.Image:
+ w, h = img.size
+ longest = max(w, h)
+ if longest == target_size:
+ return img
+ scale = target_size / float(longest)
+ new_w = max(1, int(round(w * scale)))
+ new_h = max(1, int(round(h * scale)))
+ interpolation = cv2.INTER_CUBIC if scale > 1.0 else cv2.INTER_AREA
+ arr = cv2.resize(np.asarray(img), (new_w, new_h), interpolation=interpolation)
+ return Image.fromarray(arr)
+
+ def _resize_shortest_side(self, img: Image.Image, target_size: int) -> Image.Image:
+ w, h = img.size
+ shortest = min(w, h)
+ if shortest == target_size:
+ return img
+ scale = target_size / float(shortest)
+ new_w = max(1, int(round(w * scale)))
+ new_h = max(1, int(round(h * scale)))
+ interpolation = cv2.INTER_CUBIC if scale > 1.0 else cv2.INTER_AREA
+ arr = cv2.resize(np.asarray(img), (new_w, new_h), interpolation=interpolation)
+ return Image.fromarray(arr)
+
+ # -----------------------------
+ # Make divisible by PATCH_SIZE
+ # -----------------------------
+ def _make_divisible_by_crop(self, img: Image.Image, patch: int) -> Image.Image:
+ """
+ Floor each dimension to the nearest multiple of PATCH_SIZE via center crop.
+ Example: 504x377 -> 504x364
+ """
+ w, h = img.size
+ new_w = (w // patch) * patch
+ new_h = (h // patch) * patch
+ if new_w == w and new_h == h:
+ return img
+ left = (w - new_w) // 2
+ top = (h - new_h) // 2
+ return img.crop((left, top, left + new_w, top + new_h))
+
+ def _make_divisible_by_resize(self, img: Image.Image, patch: int) -> Image.Image:
+ """
+ Round each dimension to nearest multiple of PATCH_SIZE via small resize.
+ """
+ w, h = img.size
+
+ def nearest_multiple(x: int, p: int) -> int:
+ down = (x // p) * p
+ up = down + p
+ return up if abs(up - x) <= abs(x - down) else down
+
+ new_w = max(1, nearest_multiple(w, patch))
+ new_h = max(1, nearest_multiple(h, patch))
+ if new_w == w and new_h == h:
+ return img
+ upscale = (new_w > w) or (new_h > h)
+ interpolation = cv2.INTER_CUBIC if upscale else cv2.INTER_AREA
+ arr = cv2.resize(np.asarray(img), (new_w, new_h), interpolation=interpolation)
+ return Image.fromarray(arr)
+
+
+# Backward compatibility alias
+InputAdapter = InputProcessor
+
+
+# ===========================
+# Minimal test runner (parallel execution)
+# ===========================
+if __name__ == "__main__":
+ """
+ Minimal test suite:
+ - Creates pairs of images so batch shapes match.
+ - Tests all four process_res_methods.
+ - Prints fx fy cx cy IN->OUT per image.
+ - Includes cases with K/E provided and with None.
+ """
+
+ def fmt_k_line(K: np.ndarray | None) -> str:
+ if K is None:
+ return "None"
+ fx, fy, cx, cy = float(K[0, 0]), float(K[1, 1]), float(K[0, 2]), float(K[1, 2])
+ return f"fx={fx:.3f} fy={fy:.3f} cx={cx:.3f} cy={cy:.3f}"
+
+ def show_result(
+ tag: str,
+ tensor: torch.Tensor,
+ Ks_in: Sequence[np.ndarray | None] | None = None,
+ Ks_out: Sequence[np.ndarray | None] | None = None,
+ ):
+ B, N, C, H, W = tensor.shape
+ print(f"[{tag}] shape={tuple(tensor.shape)} HxW=({H},{W}) div14=({H%14==0},{W%14==0})")
+ assert H % 14 == 0 and W % 14 == 0, f"{tag}: output size not divisible by 14!"
+ if Ks_in is not None or Ks_out is not None:
+ Ks_in = Ks_in or [None] * N
+ Ks_out = Ks_out or [None] * N
+ for i in range(N):
+ print(f" K[{i}]: {fmt_k_line(Ks_in[i])} -> {fmt_k_line(Ks_out[i])}")
+
+ proc = InputProcessor()
+ process_res = 504
+ methods = ["upper_bound_resize", "upper_bound_crop", "lower_bound_resize", "lower_bound_crop"]
+
+ # Example sizes (two orientations)
+ small_sizes = [(680, 1208), (1208, 680)]
+ large_sizes = [(1208, 680), (680, 1208)]
+
+ def make_K(w, h, fx=1200.0, fy=1100.0):
+ cx, cy = w / 2.0, h / 2.0
+ K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]], dtype=np.float32)
+ return K
+
+ def run_suite(suite_name: str, sizes: list[tuple[int, int]]):
+ print(f"\n===== {suite_name} =====")
+ for w, h in sizes:
+ img = Image.new("RGB", (w, h), color=(123, 222, 100))
+ batch_imgs = [img, img]
+
+ # intrinsics / extrinsics examples
+ Ks_in = [make_K(w, h), make_K(w, h)]
+ Es_in = [np.eye(4, dtype=np.float32), np.eye(4, dtype=np.float32)]
+
+ for m in methods:
+ tensor, Es_out, Ks_out = proc(
+ image=batch_imgs,
+ process_res=process_res,
+ process_res_method=m,
+ num_workers=8,
+ print_progress=False,
+ intrinsics=Ks_in, # test with non-None
+ extrinsics=Es_in,
+ )
+ show_result(f"{suite_name} size=({w},{h}) | {m}", tensor, Ks_in, Ks_out)
+
+ # Also test None path
+ tensor2, Es_out2, Ks_out2 = proc(
+ image=batch_imgs,
+ process_res=process_res,
+ process_res_method="upper_bound_resize",
+ num_workers=8,
+ intrinsics=None,
+ extrinsics=None,
+ )
+ show_result(
+ f"{suite_name} size=({w},{h}) | upper_bound_resize | no K/E",
+ tensor2,
+ None,
+ Ks_out2,
+ )
+
+ run_suite("SMALL", small_sizes)
+ run_suite("LARGE", large_sizes)
+
+ # Extra sanity for 504x376
+ print("\n===== EXTRA sanity for 504x376 =====")
+ img_example = Image.new("RGB", (504, 376), color=(10, 20, 30))
+ Ks_in_extra = [make_K(504, 376, fx=900.0, fy=900.0), make_K(504, 376, fx=900.0, fy=900.0)]
+
+ out_r, _, Ks_out_r = proc(
+ image=[img_example, img_example],
+ process_res=504,
+ process_res_method="upper_bound_resize",
+ num_workers=8,
+ intrinsics=Ks_in_extra,
+ )
+ out_c, _, Ks_out_c = proc(
+ image=[img_example, img_example],
+ process_res=504,
+ process_res_method="upper_bound_crop",
+ num_workers=8,
+ intrinsics=Ks_in_extra,
+ )
+ _, _, _, Hr, Wr = out_r.shape
+ _, _, _, Hc, Wc = out_c.shape
+ print(f"upper_bound_resize -> ({Hr},{Wr}) (rounded to nearest multiple of 14)")
+ show_result("Ks after upper_bound_resize", out_r, Ks_in_extra, Ks_out_r)
+ print(f"upper_bound_crop -> ({Hc},{Wc}) (floored to multiple of 14)")
+ show_result("Ks after upper_bound_crop", out_c, Ks_in_extra, Ks_out_c)
diff --git a/cameras/source/src/depth_anything_3/utils/io/output_processor.py b/cameras/source/src/depth_anything_3/utils/io/output_processor.py
new file mode 100644
index 0000000000000000000000000000000000000000..c317eb9d596c1687b5281891a035993868cc5f8c
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/io/output_processor.py
@@ -0,0 +1,172 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Output processor for Depth Anything 3.
+
+This module handles model output processing, including tensor-to-numpy conversion,
+batch dimension removal, and Prediction object creation.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+import torch
+from addict import Dict as AddictDict
+
+from depth_anything_3.specs import Prediction
+
+
+class OutputProcessor:
+ """
+ Output processor for converting model outputs to Prediction objects.
+
+ Handles tensor-to-numpy conversion, batch dimension removal,
+ and creates structured Prediction objects with proper data types.
+ """
+
+ def __init__(self) -> None:
+ """Initialize the output processor."""
+
+ def __call__(self, model_output: dict[str, torch.Tensor]) -> Prediction:
+ """
+ Convert model output to Prediction object.
+
+ Args:
+ model_output: Model output dictionary containing depth, conf, extrinsics, intrinsics
+ Expected shapes: depth (B, N, 1, H, W), conf (B, N, 1, H, W),
+ extrinsics (B, N, 4, 4), intrinsics (B, N, 3, 3)
+
+ Returns:
+ Prediction: Object containing depth estimation results with shapes:
+ depth (N, H, W), conf (N, H, W), extrinsics (N, 4, 4), intrinsics (N, 3, 3)
+ """
+ # Extract data from batch dimension (B=1, N=number of images)
+ depth = self._extract_depth(model_output)
+ conf = self._extract_conf(model_output)
+ extrinsics = self._extract_extrinsics(model_output)
+ intrinsics = self._extract_intrinsics(model_output)
+ sky = self._extract_sky(model_output)
+ aux = self._extract_aux(model_output)
+ gaussians = model_output.get("gaussians", None)
+ scale_factor = model_output.get("scale_factor", None)
+
+ return Prediction(
+ depth=depth,
+ sky=sky,
+ conf=conf,
+ extrinsics=extrinsics,
+ intrinsics=intrinsics,
+ is_metric=getattr(model_output, "is_metric", 0),
+ gaussians=gaussians,
+ aux=aux,
+ scale_factor=scale_factor,
+ )
+
+ def _extract_depth(self, model_output: dict[str, torch.Tensor]) -> np.ndarray:
+ """
+ Extract depth tensor from model output and convert to numpy.
+
+ Args:
+ model_output: Model output dictionary
+
+ Returns:
+ Depth array with shape (N, H, W)
+ """
+ depth = model_output["depth"].squeeze(0).squeeze(-1).cpu().numpy() # (N, H, W)
+ return depth
+
+ def _extract_conf(self, model_output: dict[str, torch.Tensor]) -> np.ndarray | None:
+ """
+ Extract confidence tensor from model output and convert to numpy.
+
+ Args:
+ model_output: Model output dictionary
+
+ Returns:
+ Confidence array with shape (N, H, W) or None
+ """
+ conf = model_output.get("depth_conf", None)
+ if conf is not None:
+ conf = conf.squeeze(0).cpu().numpy() # (N, H, W)
+ return conf
+
+ def _extract_extrinsics(self, model_output: dict[str, torch.Tensor]) -> np.ndarray | None:
+ """
+ Extract extrinsics tensor from model output and convert to numpy.
+
+ Args:
+ model_output: Model output dictionary
+
+ Returns:
+ Extrinsics array with shape (N, 4, 4) or None
+ """
+ extrinsics = model_output.get("extrinsics", None)
+ if extrinsics is not None:
+ extrinsics = extrinsics.squeeze(0).cpu().numpy() # (N, 4, 4)
+ return extrinsics
+
+ def _extract_intrinsics(self, model_output: dict[str, torch.Tensor]) -> np.ndarray | None:
+ """
+ Extract intrinsics tensor from model output and convert to numpy.
+
+ Args:
+ model_output: Model output dictionary
+
+ Returns:
+ Intrinsics array with shape (N, 3, 3) or None
+ """
+ intrinsics = model_output.get("intrinsics", None)
+ if intrinsics is not None:
+ intrinsics = intrinsics.squeeze(0).cpu().numpy() # (N, 3, 3)
+ return intrinsics
+
+ def _extract_sky(self, model_output: dict[str, torch.Tensor]) -> np.ndarray | None:
+ """
+ Extract sky tensor from model output and convert to numpy.
+
+ Args:
+ model_output: Model output dictionary
+
+ Returns:
+ Sky mask array with shape (N, H, W) or None
+ """
+ sky = model_output.get("sky", None)
+ if sky is not None:
+ sky = sky.squeeze(0).cpu().numpy() >= 0.5 # (N, H, W)
+ return sky
+
+ def _extract_aux(self, model_output: dict[str, torch.Tensor]) -> AddictDict:
+ """
+ Extract auxiliary data from model output and convert to numpy.
+
+ Args:
+ model_output: Model output dictionary
+
+ Returns:
+ Dictionary containing auxiliary data
+ """
+ aux = model_output.get("aux", None)
+ ret = AddictDict()
+ if aux is not None:
+ for k in aux.keys():
+ if isinstance(aux[k], torch.Tensor):
+ ret[k] = aux[k].squeeze(0).cpu().numpy()
+ else:
+ ret[k] = aux[k]
+ return ret
+
+
+# Backward compatibility alias
+OutputAdapter = OutputProcessor
diff --git a/cameras/source/src/depth_anything_3/utils/layout_helpers.py b/cameras/source/src/depth_anything_3/utils/layout_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..189c170b2007c979e580b69ca929638560923fb2
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/layout_helpers.py
@@ -0,0 +1,216 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This file contains useful layout utilities for images. They are:
+
+- add_border: Add a border to an image.
+- cat/hcat/vcat: Join images by arranging them in a line. If the images have different
+ sizes, they are aligned as specified (start, end, center). Allows you to specify a gap
+ between images.
+
+Images are assumed to be float32 tensors with shape (channel, height, width).
+"""
+
+from typing import Any, Generator, Iterable, Literal, Union
+import torch
+from torch import Tensor
+
+Alignment = Literal["start", "center", "end"]
+Axis = Literal["horizontal", "vertical"]
+Color = Union[
+ int,
+ float,
+ Iterable[int],
+ Iterable[float],
+ Tensor,
+ Tensor,
+]
+
+
+def _sanitize_color(color: Color) -> Tensor: # "#channel"
+ # Convert tensor to list (or individual item).
+ if isinstance(color, torch.Tensor):
+ color = color.tolist()
+
+ # Turn iterators and individual items into lists.
+ if isinstance(color, Iterable):
+ color = list(color)
+ else:
+ color = [color]
+
+ return torch.tensor(color, dtype=torch.float32)
+
+
+def _intersperse(iterable: Iterable, delimiter: Any) -> Generator[Any, None, None]:
+ it = iter(iterable)
+ yield next(it)
+ for item in it:
+ yield delimiter
+ yield item
+
+
+def _get_main_dim(main_axis: Axis) -> int:
+ return {
+ "horizontal": 2,
+ "vertical": 1,
+ }[main_axis]
+
+
+def _get_cross_dim(main_axis: Axis) -> int:
+ return {
+ "horizontal": 1,
+ "vertical": 2,
+ }[main_axis]
+
+
+def _compute_offset(base: int, overlay: int, align: Alignment) -> slice:
+ assert base >= overlay
+ offset = {
+ "start": 0,
+ "center": (base - overlay) // 2,
+ "end": base - overlay,
+ }[align]
+ return slice(offset, offset + overlay)
+
+
+def overlay(
+ base: Tensor, # "channel base_height base_width"
+ overlay: Tensor, # "channel overlay_height overlay_width"
+ main_axis: Axis,
+ main_axis_alignment: Alignment,
+ cross_axis_alignment: Alignment,
+) -> Tensor: # "channel base_height base_width"
+ # The overlay must be smaller than the base.
+ _, base_height, base_width = base.shape
+ _, overlay_height, overlay_width = overlay.shape
+ assert base_height >= overlay_height and base_width >= overlay_width
+
+ # Compute spacing on the main dimension.
+ main_dim = _get_main_dim(main_axis)
+ main_slice = _compute_offset(
+ base.shape[main_dim], overlay.shape[main_dim], main_axis_alignment
+ )
+
+ # Compute spacing on the cross dimension.
+ cross_dim = _get_cross_dim(main_axis)
+ cross_slice = _compute_offset(
+ base.shape[cross_dim], overlay.shape[cross_dim], cross_axis_alignment
+ )
+
+ # Combine the slices and paste the overlay onto the base accordingly.
+ selector = [..., None, None]
+ selector[main_dim] = main_slice
+ selector[cross_dim] = cross_slice
+ result = base.clone()
+ result[selector] = overlay
+ return result
+
+
+def cat(
+ main_axis: Axis,
+ *images: Iterable[Tensor], # "channel _ _"
+ align: Alignment = "center",
+ gap: int = 8,
+ gap_color: Color = 1,
+) -> Tensor: # "channel height width"
+ """Arrange images in a line. The interface resembles a CSS div with flexbox."""
+ device = images[0].device
+ gap_color = _sanitize_color(gap_color).to(device)
+
+ # Find the maximum image side length in the cross axis dimension.
+ cross_dim = _get_cross_dim(main_axis)
+ cross_axis_length = max(image.shape[cross_dim] for image in images)
+
+ # Pad the images.
+ padded_images = []
+ for image in images:
+ # Create an empty image with the correct size.
+ padded_shape = list(image.shape)
+ padded_shape[cross_dim] = cross_axis_length
+ base = torch.ones(padded_shape, dtype=torch.float32, device=device)
+ base = base * gap_color[:, None, None]
+ padded_images.append(overlay(base, image, main_axis, "start", align))
+
+ # Intersperse separators if necessary.
+ if gap > 0:
+ # Generate a separator.
+ c, _, _ = images[0].shape
+ separator_size = [gap, gap]
+ separator_size[cross_dim - 1] = cross_axis_length
+ separator = torch.ones((c, *separator_size), dtype=torch.float32, device=device)
+ separator = separator * gap_color[:, None, None]
+
+ # Intersperse the separator between the images.
+ padded_images = list(_intersperse(padded_images, separator))
+
+ return torch.cat(padded_images, dim=_get_main_dim(main_axis))
+
+
+def hcat(
+ *images: Iterable[Tensor], # "channel _ _"
+ align: Literal["start", "center", "end", "top", "bottom"] = "start",
+ gap: int = 8,
+ gap_color: Color = 1,
+):
+ """Shorthand for a horizontal linear concatenation."""
+ return cat(
+ "horizontal",
+ *images,
+ align={
+ "start": "start",
+ "center": "center",
+ "end": "end",
+ "top": "start",
+ "bottom": "end",
+ }[align],
+ gap=gap,
+ gap_color=gap_color,
+ )
+
+
+def vcat(
+ *images: Iterable[Tensor], # "channel _ _"
+ align: Literal["start", "center", "end", "left", "right"] = "start",
+ gap: int = 8,
+ gap_color: Color = 1,
+):
+ """Shorthand for a horizontal linear concatenation."""
+ return cat(
+ "vertical",
+ *images,
+ align={
+ "start": "start",
+ "center": "center",
+ "end": "end",
+ "left": "start",
+ "right": "end",
+ }[align],
+ gap=gap,
+ gap_color=gap_color,
+ )
+
+
+def add_border(
+ image: Tensor, # "channel height width"
+ border: int = 8,
+ color: Color = 1,
+) -> Tensor: # "channel new_height new_width"
+ color = _sanitize_color(color).to(image)
+ c, h, w = image.shape
+ result = torch.empty(
+ (c, h + 2 * border, w + 2 * border), dtype=torch.float32, device=image.device
+ )
+ result[:] = color[:, None, None]
+ result[:, border : h + border, border : w + border] = image
+ return result
diff --git a/cameras/source/src/depth_anything_3/utils/logger.py b/cameras/source/src/depth_anything_3/utils/logger.py
new file mode 100644
index 0000000000000000000000000000000000000000..0eb4f60696a085001cf4866ccfe1654170702a2d
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/logger.py
@@ -0,0 +1,82 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+
+class Color:
+ RED = "\033[91m"
+ YELLOW = "\033[93m"
+ WHITE = "\033[97m"
+ GREEN = "\033[92m"
+ RESET = "\033[0m"
+
+
+LOG_LEVELS = {"ERROR": 0, "WARN": 1, "INFO": 2, "DEBUG": 3}
+
+COLOR_MAP = {"ERROR": Color.RED, "WARN": Color.YELLOW, "INFO": Color.WHITE, "DEBUG": Color.GREEN}
+
+
+def get_env_log_level():
+ level = os.environ.get("DA3_LOG_LEVEL", "INFO").upper()
+ return LOG_LEVELS.get(level, LOG_LEVELS["INFO"])
+
+
+class Logger:
+ def __init__(self):
+ self.level = get_env_log_level()
+
+ def log(self, level_str, *args, **kwargs):
+ level_key = level_str.split(":")[0].strip()
+ level_val = LOG_LEVELS.get(level_key)
+ if level_val is None:
+ raise ValueError(f"Unknown log level: {level_str}")
+ if self.level >= level_val:
+ color = COLOR_MAP[level_key]
+ msg = " ".join(str(arg) for arg in args)
+
+ # Align log level output in square brackets
+ # ERROR and DEBUG are 5 characters, INFO and WARN have an extra space for alignment
+ tag = level_key
+ if tag in ("INFO", "WARN"):
+ tag += " "
+ print(
+ f"{color}[{tag}] {msg}{Color.RESET}",
+ file=sys.stderr if level_key == "ERROR" else sys.stdout,
+ **kwargs,
+ )
+
+ def error(self, *args, **kwargs):
+ self.log("ERROR:", *args, **kwargs)
+
+ def warn(self, *args, **kwargs):
+ self.log("WARN:", *args, **kwargs)
+
+ def info(self, *args, **kwargs):
+ self.log("INFO:", *args, **kwargs)
+
+ def debug(self, *args, **kwargs):
+ self.log("DEBUG:", *args, **kwargs)
+
+
+logger = Logger()
+
+__all__ = ["logger"]
+
+if __name__ == "__main__":
+ logger.info("This is an info message")
+ logger.warn("This is a warning message")
+ logger.error("This is an error message")
+ logger.debug("This is a debug message")
diff --git a/cameras/source/src/depth_anything_3/utils/memory.py b/cameras/source/src/depth_anything_3/utils/memory.py
new file mode 100644
index 0000000000000000000000000000000000000000..682dad76b2fccd606246144788cec0198eed2a62
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/memory.py
@@ -0,0 +1,127 @@
+"""
+GPU memory utility helpers.
+
+Shared cleanup and memory checking logic used by both the backend API and
+the Gradio UI to keep memory-management behavior consistent.
+"""
+from __future__ import annotations
+
+import gc
+
+from typing import Any, Dict, Optional
+
+import torch
+
+
+def get_gpu_memory_info() -> Optional[Dict[str, Any]]:
+ """Return a snapshot of current GPU memory usage or None if CUDA not available.
+
+ Keys in returned dict: total_gb, allocated_gb, reserved_gb, free_gb, utilization
+ """
+ if not torch.cuda.is_available():
+ return None
+
+ try:
+ device = torch.cuda.current_device()
+ total_memory = torch.cuda.get_device_properties(device).total_memory
+ allocated_memory = torch.cuda.memory_allocated(device)
+ reserved_memory = torch.cuda.memory_reserved(device)
+ free_memory = total_memory - reserved_memory
+
+ return {
+ "total_gb": total_memory / 1024 ** 3,
+ "allocated_gb": allocated_memory / 1024 ** 3,
+ "reserved_gb": reserved_memory / 1024 ** 3,
+ "free_gb": free_memory / 1024 ** 3,
+ "utilization": (reserved_memory / total_memory) * 100,
+ }
+ except Exception:
+ return None
+
+
+def cleanup_cuda_memory() -> None:
+ """Perform a robust GPU cleanup sequence.
+
+ This includes synchronizing, emptying caches, collecting IPC handles and
+ running the Python garbage collector. Use this instead of a raw
+ ``torch.cuda.empty_cache()`` where you need reliable freeing of GPU memory
+ between model loads or in error handling paths.
+ """
+ try:
+ if torch.cuda.is_available():
+ mem_before = get_gpu_memory_info()
+
+ torch.cuda.synchronize()
+ torch.cuda.empty_cache()
+ # Collect cross-process cuda resources
+ try:
+ torch.cuda.ipc_collect()
+ except Exception:
+ # Older PyTorch versions or non-cuda devices may not support
+ # ipc_collect (no-op if not available)
+ pass
+ gc.collect()
+
+ mem_after = get_gpu_memory_info()
+ if mem_before and mem_after:
+ freed = mem_before["reserved_gb"] - mem_after["reserved_gb"]
+ print(
+ f"CUDA cleanup: freed {freed:.2f}GB, "
+ f"available: {mem_after['free_gb']:.2f}GB/{mem_after['total_gb']:.2f}GB"
+ )
+ else:
+ print("CUDA memory cleanup completed")
+ except Exception as e:
+ print(f"Warning: CUDA cleanup failed: {e}")
+
+
+def check_memory_availability(required_gb: float = 2.0) -> tuple[bool, str]:
+ """Return whether at least ``required_gb`` seems available on the current GPU.
+
+ The returned tuple is (is_available, message) with a human-friendly message.
+ """
+ try:
+ if not torch.cuda.is_available():
+ return False, "CUDA is not available"
+
+ mem_info = get_gpu_memory_info()
+ if mem_info is None:
+ return True, "Cannot check memory, proceeding anyway"
+
+ if mem_info["free_gb"] < required_gb:
+ return (
+ False,
+ (
+ f"Insufficient GPU memory: {mem_info['free_gb']:.2f}GB available, "
+ f"{required_gb:.2f}GB required. Total: {mem_info['total_gb']:.2f}GB, "
+ f"Used: {mem_info['reserved_gb']:.2f}GB ({mem_info['utilization']:.1f}%)"
+ ),
+ )
+
+ return (
+ True,
+ (
+ f"Memory check passed: {mem_info['free_gb']:.2f}GB available, "
+ f"{required_gb:.2f}GB required"
+ ),
+ )
+ except Exception as e:
+ return True, f"Memory check failed: {e}, proceeding anyway"
+def estimate_memory_requirement(num_images: int, process_res: int) -> float:
+ """Heuristic estimate for memory usage (GB) based on image count and resolution.
+
+ This mirrors the simple policy used by the backend service so other code
+ (e.g., Gradio UI) can make consistent decisions when checking available
+ memory before loading a model or running inference.
+
+ Args:
+ num_images: Number of images to process.
+ process_res: Processing resolution.
+
+ Returns:
+ Estimated memory requirement in GB.
+ """
+ base_memory = 2.0
+ per_image_memory = (process_res / 504) ** 2 * 0.5
+ total_memory = base_memory + (num_images * per_image_memory * 0.1)
+ return total_memory
diff --git a/cameras/source/src/depth_anything_3/utils/model_loading.py b/cameras/source/src/depth_anything_3/utils/model_loading.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6d43b5bbab5a0989eae272422192103cd4d5bee
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/model_loading.py
@@ -0,0 +1,149 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Model loading and state dict conversion utilities.
+"""
+
+from typing import Dict, Tuple
+import torch
+
+from depth_anything_3.utils.logger import logger
+
+
+def convert_general_state_dict(state_dict: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]:
+ """
+ Convert general model state dict to match current model architecture.
+
+ Args:
+ state_dict: Original state dictionary
+
+ Returns:
+ Converted state dictionary
+ """
+ # Replace module prefixes
+ state_dict = {k.replace("module.", "model."): v for k, v in state_dict.items()}
+ state_dict = {k.replace(".net.", ".backbone."): v for k, v in state_dict.items()}
+
+ # Remove camera token if present
+ if "model.backbone.pretrained.camera_token" in state_dict:
+ del state_dict["model.backbone.pretrained.camera_token"]
+
+ # Replace camera token naming
+ state_dict = {
+ k.replace(".camera_token_extra", ".camera_token"): v for k, v in state_dict.items()
+ }
+
+ # Replace head naming
+ state_dict = {
+ k.replace("model.all_heads.camera_cond_head", "model.cam_enc"): v
+ for k, v in state_dict.items()
+ }
+ state_dict = {
+ k.replace("model.all_heads.camera_head", "model.cam_dec"): v for k, v in state_dict.items()
+ }
+ state_dict = {k.replace(".more_mlps.", ".backbone."): v for k, v in state_dict.items()}
+ state_dict = {k.replace(".fc_rot.", ".fc_qvec."): v for k, v in state_dict.items()}
+ state_dict = {
+ k.replace("model.all_heads.head", "model.head"): v for k, v in state_dict.items()
+ }
+
+ # Replace output naming
+ state_dict = {
+ k.replace("output_conv2_additional.sky_mask", "sky_output_conv2"): v
+ for k, v in state_dict.items()
+ }
+ state_dict = {k.replace("_ray.", "_aux."): v for k, v in state_dict.items()}
+
+ # Update GS-DPT head naming and value
+ state_dict = {k.replace("gaussian_param_head.", "gs_head."): v for k, v in state_dict.items()}
+
+ return state_dict
+
+
+def convert_metric_state_dict(state_dict: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]:
+ """
+ Convert metric model state dict to match current model architecture.
+
+ Args:
+ state_dict: Original metric state dictionary
+
+ Returns:
+ Converted state dictionary
+ """
+ # Add module prefix for metric models
+ state_dict = {"module." + k: v for k, v in state_dict.items()}
+ return convert_general_state_dict(state_dict)
+
+
+def load_pretrained_weights(model, model_path: str, is_metric: bool = False) -> Tuple[list, list]:
+ """
+ Load pretrained weights for a single model.
+
+ Args:
+ model: Model instance to load weights into
+ model_path: Path to the pretrained weights
+ is_metric: Whether this is a metric model
+
+ Returns:
+ Tuple of (missed_keys, unexpected_keys)
+ """
+ state_dict = torch.load(model_path, map_location="cpu")
+
+ if is_metric:
+ state_dict = convert_metric_state_dict(state_dict)
+ else:
+ state_dict = convert_general_state_dict(state_dict)
+
+ missed, unexpected = model.load_state_dict(state_dict, strict=False)
+ logger.info("Missed keys:", missed)
+ logger.info("Unexpected keys:", unexpected)
+
+ return missed, unexpected
+
+
+def load_pretrained_nested_weights(
+ model, main_model_path: str, metric_model_path: str
+) -> Tuple[list, list]:
+ """
+ Load pretrained weights for a nested model with both main and metric branches.
+
+ Args:
+ model: Nested model instance
+ main_model_path: Path to main model weights
+ metric_model_path: Path to metric model weights
+
+ Returns:
+ Tuple of (missed_keys, unexpected_keys)
+ """
+ # Load main model weights
+ state_dict0 = torch.load(main_model_path, map_location="cpu")
+ state_dict0 = convert_general_state_dict(state_dict0)
+ state_dict0 = {k.replace("model.", "model.da3."): v for k, v in state_dict0.items()}
+
+ # Load metric model weights
+ state_dict1 = torch.load(metric_model_path, map_location="cpu")
+ state_dict1 = convert_metric_state_dict(state_dict1)
+ state_dict1 = {k.replace("model.", "model.da3_metric."): v for k, v in state_dict1.items()}
+
+ # Combine state dictionaries
+ combined_state_dict = state_dict0.copy()
+ combined_state_dict.update(state_dict1)
+
+ missed, unexpected = model.load_state_dict(combined_state_dict, strict=False)
+
+ print("Missed keys:", missed)
+ print("Unexpected keys:", unexpected)
+
+ return missed, unexpected
diff --git a/cameras/source/src/depth_anything_3/utils/parallel_utils.py b/cameras/source/src/depth_anything_3/utils/parallel_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ff108e95d205097f9f2012ab87ad9e265d58d8d
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/parallel_utils.py
@@ -0,0 +1,133 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import asyncio
+import os
+from functools import wraps
+from multiprocessing.pool import ThreadPool
+from threading import Thread
+from typing import Callable, Dict, List
+import imageio
+from tqdm import tqdm
+
+
+def async_call_func(func):
+ @wraps(func)
+ async def wrapper(*args, **kwargs):
+ loop = asyncio.get_event_loop()
+ # Use run_in_executor to run the blocking function in a separate thread
+ return await loop.run_in_executor(None, func, *args, **kwargs)
+
+ return wrapper
+
+
+slice_func = lambda chunk_index, chunk_dim, chunk_size: [slice(None)] * chunk_dim + [
+ slice(chunk_index, chunk_index + chunk_size)
+]
+
+
+def async_call(fn):
+ def wrapper(*args, **kwargs):
+ Thread(target=fn, args=args, kwargs=kwargs).start()
+
+ return wrapper
+
+
+def _save_image_impl(save_img, save_path):
+ """Common implementation for saving images synchronously or asynchronously"""
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
+ imageio.imwrite(save_path, save_img)
+
+
+@async_call
+def save_image_async(save_img, save_path):
+ """Save image asynchronously"""
+ _save_image_impl(save_img, save_path)
+
+
+def save_image(save_img, save_path):
+ """Save image synchronously"""
+ _save_image_impl(save_img, save_path)
+
+
+def parallel_execution(
+ *args,
+ action: Callable,
+ num_processes=32,
+ print_progress=False,
+ sequential=False,
+ async_return=False,
+ desc=None,
+ **kwargs,
+):
+ # Partially copy from EasyVolumetricVideo (parallel_execution)
+ # NOTE: we expect first arg / or kwargs to be distributed
+ # NOTE: print_progress arg is reserved.
+ # `*args` packs all positional arguments passed to the function into a tuple
+ args = list(args)
+
+ def get_length(args: List, kwargs: Dict):
+ for a in args:
+ if isinstance(a, list):
+ return len(a)
+ for v in kwargs.values():
+ if isinstance(v, list):
+ return len(v)
+ raise NotImplementedError
+
+ def get_action_args(length: int, args: List, kwargs: Dict, i: int):
+ action_args = [
+ (arg[i] if isinstance(arg, list) and len(arg) == length else arg) for arg in args
+ ]
+ # TODO: Support all types of iterable
+ action_kwargs = {
+ key: (
+ kwargs[key][i]
+ if isinstance(kwargs[key], list) and len(kwargs[key]) == length
+ else kwargs[key]
+ )
+ for key in kwargs
+ }
+ return action_args, action_kwargs
+
+ if not sequential:
+ # Create ThreadPool
+ pool = ThreadPool(processes=num_processes)
+
+ # Spawn threads
+ results = []
+ asyncs = []
+ length = get_length(args, kwargs)
+ for i in range(length):
+ action_args, action_kwargs = get_action_args(length, args, kwargs, i)
+ async_result = pool.apply_async(action, action_args, action_kwargs)
+ asyncs.append(async_result)
+
+ # Join threads and get return values
+ if not async_return:
+ for async_result in tqdm(asyncs, desc=desc, disable=not print_progress):
+ results.append(async_result.get()) # will sync the corresponding thread
+ pool.close()
+ pool.join()
+ return results
+ else:
+ return pool
+ else:
+ results = []
+ length = get_length(args, kwargs)
+ for i in tqdm(range(length), desc=desc, disable=not print_progress):
+ action_args, action_kwargs = get_action_args(length, args, kwargs, i)
+ async_result = action(*action_args, **action_kwargs)
+ results.append(async_result)
+ return results
diff --git a/cameras/source/src/depth_anything_3/utils/pca_utils.py b/cameras/source/src/depth_anything_3/utils/pca_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b9eee268cd8692d885bf093700a5752077b9d7d
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/pca_utils.py
@@ -0,0 +1,284 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+PCA utilities for feature visualization and dimensionality reduction (video-friendly).
+- Support frame-by-frame: transform_frame / transform_video
+- Support one-time global PCA fitting and reuse (mean, V3) for stable colors
+- Support Procrustes alignment (solving principal component order/sign/rotation jumps)
+- Support global fixed or temporal EMA for percentiles (time dimension only, no spatial)
+"""
+
+import numpy as np
+import torch
+
+
+def pca_to_rgb_4d_bf16_percentile(
+ x_np: np.ndarray,
+ device=None,
+ q_oversample: int = 6,
+ clip_percent: float = 10.0, # Percentage to clip from top and bottom (0~49.9)
+ return_uint8: bool = False,
+ enable_autocast_bf16: bool = True,
+):
+ """
+ Reduce numpy array of shape (49, 27, 36, 3072) to 3D via PCA and visualize as (49, 27, 36, 3).
+ - PCA uses torch.pca_lowrank (randomized SVD), defaults to GPU.
+ - Uses CUDA bf16 autocast in computation (if available),
+ then per-channel percentile clipping and normalization.
+ - Default removes 5% outliers from top and bottom (adjustable via clip_percent) to
+ improve visualization contrast.
+
+ Parameters
+ ----------
+ x_np : np.ndarray
+ Shape must be (49, 27, 36, 3072). dtype recommended float32/float64.
+ device : str | None
+ Specify 'cuda' or 'cpu'. Auto-select if None (prefer cuda).
+ q_oversample : int
+ Oversampling q for pca_lowrank, must be >= 3.
+ Slightly larger than target dim (3) is more stable, default 6.
+ clip_percent : float
+ Percentage to clip from top and bottom (0~49.9),
+ e.g. 5.0 means clip lowest 5% and highest 5% per channel.
+ return_uint8 : bool
+ True returns uint8(0~255), otherwise returns float32(0~1).
+ enable_autocast_bf16 : bool
+ Enable bf16 autocast on CUDA.
+
+ Returns
+ -------
+ np.ndarray
+ Array of shape (49, 27, 36, 3), float32[0,1] or uint8[0,255].
+ """
+ assert (
+ x_np.ndim == 4
+ ) # and x_np.shape[-1] == 3072, f"expect (49,27,36,3072), got {x_np.shape}"
+ B1, B2, B3, D = x_np.shape
+ N = B1 * B2 * B3
+
+ # Device selection
+ if device is None:
+ device = "cuda" if torch.cuda.is_available() else "cpu"
+
+ # Convert input to torch, unified float32
+ X = torch.from_numpy(x_np.reshape(N, D)).to(device=device, dtype=torch.float32)
+
+ # Parameter and safety checks
+ k = 3
+ q = max(int(q_oversample), k)
+ clip_percent = float(clip_percent)
+ if not (0.0 <= clip_percent < 50.0):
+ raise ValueError(
+ "clip_percent must be in [0, 50), e.g. 5.0 means clip 5% from top and bottom"
+ )
+ low = clip_percent / 100.0
+ high = 1.0 - low
+
+ with torch.no_grad():
+ # Zero mean
+ mean = X.mean(dim=0, keepdim=True)
+ Xc = X - mean
+
+ # Main computation: PCA + projection, try to use bf16
+ # (auto-fallback if operator not supported)
+ device.startswith("cuda") and enable_autocast_bf16
+ U, S, V = torch.pca_lowrank(Xc, q=q, center=False) # V: (D, q)
+ V3 = V[:, :k] # (3072, 3)
+ PCs = Xc @ V3 # (N, 3)
+
+ # === Per-channel percentile clipping and normalization to [0,1] ===
+ # Vectorized one-time calculation of low/high percentiles for each channel
+ qs = torch.tensor([low, high], device=PCs.device, dtype=PCs.dtype)
+ qvals = torch.quantile(PCs, q=qs, dim=0) # Shape (2, 3)
+ lo = qvals[0] # (3,)
+ hi = qvals[1] # (3,)
+
+ # Avoid degenerate case where hi==lo
+ denom = torch.clamp(hi - lo, min=1e-8)
+
+ # Broadcast clipping + normalization
+ PCs = torch.clamp(PCs, lo, hi)
+ PCs = (PCs - lo) / denom # (N, 3) in [0,1]
+
+ # Restore 4D
+ PCs = PCs.reshape(B1, B2, B3, k)
+
+ # Output
+ if return_uint8:
+ out = (PCs * 255.0).round().clamp(0, 255).to(torch.uint8).cpu().numpy()
+ else:
+ out = PCs.clamp(0, 1).to(torch.float32).cpu().numpy()
+
+ return out
+
+
+class PCARGBVisualizer:
+ """
+ Stable PCA→RGB for video features shaped (T, H, W, D) or a single frame (H, W, D).
+ - Global mean/V3 reference for stable colors
+ - Per-frame PCA with Procrustes alignment to V3_ref (basis_mode='procrustes')
+ - Percentile normalization with global or EMA stats (time-only, no spatial smoothing)
+ """
+
+ def __init__(
+ self,
+ device=None,
+ q_oversample: int = 16,
+ clip_percent: float = 10.0,
+ return_uint8: bool = False,
+ enable_autocast_bf16: bool = True,
+ basis_mode: str = "procrustes", # 'fixed' | 'procrustes'
+ percentile_mode: str = "ema", # 'global' | 'ema'
+ ema_alpha: float = 0.1,
+ denom_eps: float = 1e-4,
+ ):
+ assert 0.0 <= clip_percent < 50.0
+ assert basis_mode in ("fixed", "procrustes")
+ assert percentile_mode in ("global", "ema")
+ self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.q = max(int(q_oversample), 6)
+ self.clip_percent = float(clip_percent)
+ self.return_uint8 = return_uint8
+ self.enable_autocast_bf16 = enable_autocast_bf16
+ self.basis_mode = basis_mode
+ self.percentile_mode = percentile_mode
+ self.ema_alpha = float(ema_alpha)
+ self.denom_eps = float(denom_eps)
+
+ # reference state
+ self.mean_ref = None # (1, D)
+ self.V3_ref = None # (D, 3)
+ self.lo_ref = None # (3,)
+ self.hi_ref = None # (3,)
+
+ @torch.no_grad()
+ def fit_reference(self, frames):
+ """
+ Fit global mean/V3 and initialize percentiles from a reference set.
+ frames: ndarray (T,H,W,D) or list of (H,W,D)
+ """
+ if isinstance(frames, np.ndarray):
+ if frames.ndim != 4:
+ raise ValueError("fit_reference expects (T,H,W,D) ndarray.")
+ T, H, W, D = frames.shape
+ X = torch.from_numpy(frames.reshape(T * H * W, D))
+ else: # list of (H,W,D)
+ xs = [torch.from_numpy(x.reshape(-1, x.shape[-1])) for x in frames]
+ D = xs[0].shape[-1]
+ X = torch.cat(xs, dim=0)
+
+ X = X.to(self.device, dtype=torch.float32)
+ X = torch.nan_to_num(X, nan=0.0, posinf=1e6, neginf=-1e6)
+
+ mean = X.mean(0, keepdim=True)
+ Xc = X - mean
+
+ U, S, V = torch.pca_lowrank(Xc, q=max(self.q, 8), center=False)
+ V3 = V[:, :3] # (D,3)
+
+ PCs = Xc @ V3
+ low = self.clip_percent / 100.0
+ high = 1.0 - low
+ qs = torch.tensor([low, high], device=PCs.device, dtype=PCs.dtype)
+ qvals = torch.quantile(PCs, q=qs, dim=0)
+ lo, hi = qvals[0], qvals[1]
+
+ self.mean_ref = mean
+ self.V3_ref = V3
+ if self.percentile_mode == "global":
+ self.lo_ref, self.hi_ref = lo, hi
+ else:
+ self.lo_ref = lo.clone()
+ self.hi_ref = hi.clone()
+
+ @torch.no_grad()
+ def _project_with_stable_colors(self, X: torch.Tensor) -> torch.Tensor:
+ """
+ X: (N,D) where N = H*W
+ Returns PCs_raw: (N,3) using stable basis (fixed or Procrustes-aligned)
+ """
+ assert self.mean_ref is not None and self.V3_ref is not None, "Call fit_reference() first."
+ X = torch.nan_to_num(X, nan=0.0, posinf=1e6, neginf=-1e6)
+ Xc = X - self.mean_ref
+
+ if self.basis_mode == "fixed":
+ V3_used = self.V3_ref
+ else:
+ U, S, V = torch.pca_lowrank(Xc, q=max(self.q, 6), center=False)
+ V3 = V[:, :3] # (D,3)
+ M = V3.T @ self.V3_ref
+ Uo, So, Vh = torch.linalg.svd(M)
+ R = Uo @ Vh
+ V3_used = V3 @ R
+ # Optional polarity fix via anchor
+ a = self.V3_ref.mean(0, keepdim=True)
+ sign = torch.sign((V3_used * a).sum(0, keepdim=True)).clamp(min=-1)
+ V3_used = V3_used * sign
+
+ return Xc @ V3_used
+
+ @torch.no_grad()
+ def _normalize_rgb(self, PCs_raw: torch.Tensor) -> torch.Tensor:
+ assert self.lo_ref is not None and self.hi_ref is not None
+ if self.percentile_mode == "global":
+ lo, hi = self.lo_ref, self.hi_ref
+ else:
+ low = self.clip_percent / 100.0
+ high = 1.0 - low
+ qs = torch.tensor([low, high], device=PCs_raw.device, dtype=PCs_raw.dtype)
+ qvals = torch.quantile(PCs_raw, q=qs, dim=0)
+ lo_now, hi_now = qvals[0], qvals[1]
+ a = self.ema_alpha
+ self.lo_ref = (1 - a) * self.lo_ref + a * lo_now
+ self.hi_ref = (1 - a) * self.hi_ref + a * hi_now
+ lo, hi = self.lo_ref, self.hi_ref
+
+ denom = torch.clamp(hi - lo, min=self.denom_eps)
+ PCs = torch.clamp(PCs_raw, lo, hi)
+ PCs = (PCs - lo) / denom
+ return PCs.clamp_(0, 1)
+
+ @torch.no_grad()
+ def transform_frame(self, frame: np.ndarray) -> np.ndarray:
+ """
+ frame: (H,W,D) -> (H,W,3)
+ """
+ if frame.ndim != 3:
+ raise ValueError("transform_frame expects (H,W,D).")
+ H, W, D = frame.shape
+ X = torch.from_numpy(frame.reshape(H * W, D)).to(self.device, dtype=torch.float32)
+ PCs_raw = self._project_with_stable_colors(X)
+ PCs = self._normalize_rgb(PCs_raw).reshape(H, W, 3)
+ if self.return_uint8:
+ return (PCs * 255.0).round().clamp(0, 255).to(torch.uint8).cpu().numpy()
+ return PCs.to(torch.float32).cpu().numpy()
+
+ @torch.no_grad()
+ def transform_video(self, frames) -> np.ndarray:
+ """
+ frames: (T,H,W,D) or list of (H,W,D)
+ returns: (T,H,W,3)
+ """
+ outs = []
+ if isinstance(frames, np.ndarray):
+ if frames.ndim != 4:
+ raise ValueError("transform_video expects (T,H,W,D).")
+ T, H, W, D = frames.shape
+ for t in range(T):
+ outs.append(self.transform_frame(frames[t]))
+ else:
+ for f in frames:
+ outs.append(self.transform_frame(f))
+ return np.stack(outs, axis=0)
diff --git a/cameras/source/src/depth_anything_3/utils/pose_align.py b/cameras/source/src/depth_anything_3/utils/pose_align.py
new file mode 100644
index 0000000000000000000000000000000000000000..695d07fc1210e3cc3614c9b22c56d765b1106500
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/pose_align.py
@@ -0,0 +1,347 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import List
+import numpy as np
+import torch
+from evo.core.trajectory import PosePath3D
+
+from depth_anything_3.utils.geometry import affine_inverse, affine_inverse_np
+
+
+def batch_apply_alignment_to_enc(
+ rots: torch.Tensor, trans: torch.Tensor, scales: torch.Tensor, enc_list: List[torch.Tensor]
+):
+ pass
+
+
+def batch_apply_alignment_to_ext(
+ rots: torch.Tensor, trans: torch.Tensor, scales: torch.Tensor, ext: torch.Tensor
+):
+ device, _ = ext.device, ext.dtype
+ if ext.shape[-2:] == (3, 4):
+ pad = torch.zeros((*ext.shape[:-2], 4, 4), dtype=ext.dtype, device=device)
+ pad[..., :3, :4] = ext
+ pad[..., 3, 3] = 1.0
+ ext = pad
+ pose_est = affine_inverse(ext)
+ pose_new_align_rot = rots[:, None] @ pose_est[..., :3, :3]
+ pose_new_align_trans = (
+ scales[:, None, None] * (rots[:, None] @ pose_est[..., :3, 3:])[..., 0] + trans[:, None]
+ )
+ pose_new_align = torch.zeros_like(ext)
+ pose_new_align[..., :3, :3] = pose_new_align_rot
+ pose_new_align[..., :3, 3] = pose_new_align_trans
+ pose_new_align[..., 3, 3] = 1.0
+ return affine_inverse(pose_new_align)[:, :3]
+
+
+def batch_align_poses_umeyama(ext_ref: torch.Tensor, ext_est: torch.Tensor):
+ device, dtype = ext_ref.device, ext_ref.dtype
+ assert ext_ref.dtype in [torch.float32, torch.float64]
+ assert ext_est.dtype in [torch.float32, torch.float64]
+ assert ext_ref.requires_grad is False
+ assert ext_est.requires_grad is False
+ rots, trans, scales = [], [], []
+ for b in range(ext_ref.shape[0]):
+ r, t, s = align_poses_umeyama(ext_ref[b].cpu().numpy(), ext_est[b].cpu().numpy())
+ rots.append(torch.from_numpy(r).to(device=device, dtype=dtype))
+ trans.append(torch.from_numpy(t).to(device=device, dtype=dtype))
+ scales.append(torch.tensor(s, device=device, dtype=dtype))
+ return torch.stack(rots), torch.stack(trans), torch.stack(scales)
+
+
+# Dependencies: affine_inverse_np, PosePath3D (maintain consistency with your existing project)
+
+
+def _to44(ext):
+ if ext.shape[1] == 3:
+ out = np.eye(4)[None].repeat(len(ext), 0)
+ out[:, :3, :4] = ext
+ return out
+ return ext
+
+
+def _poses_from_ext(ext_ref, ext_est):
+ ext_ref = _to44(ext_ref)
+ ext_est = _to44(ext_est)
+ pose_ref = affine_inverse_np(ext_ref)
+ pose_est = affine_inverse_np(ext_est)
+ return pose_ref, pose_est
+
+
+def _umeyama_sim3_from_paths(pose_ref, pose_est):
+ path_ref = PosePath3D(poses_se3=pose_ref.copy())
+ path_est = PosePath3D(poses_se3=pose_est.copy())
+ r, t, s = path_est.align(path_ref, correct_scale=True)
+ pose_est_aligned = np.stack(path_est.poses_se3)
+ return r, t, s, pose_est_aligned
+
+
+def _apply_sim3_to_poses(poses, r, t, s):
+ out = poses.copy()
+ Ri = poses[:, :3, :3]
+ ti = poses[:, :3, 3]
+ out[:, :3, :3] = r @ Ri
+ out[:, :3, 3] = (r @ (s * ti.T)).T + t
+ return out
+
+
+def _median_nn_thresh(pose_ref, pose_est_aligned):
+ P_ref = pose_ref[:, :3, 3]
+ P_est = pose_est_aligned[:, :3, 3]
+ dists = []
+ for p in P_est:
+ dd = np.linalg.norm(P_ref - p[None, :], axis=1)
+ dists.append(dd.min())
+ return float(np.median(dists)) if dists else 0.0
+
+
+def _ransac_align_sim3(
+ pose_ref, pose_est, sub_n=None, inlier_thresh=None, max_iters=10, random_state=None
+):
+ rng = np.random.default_rng(random_state)
+ N = pose_ref.shape[0]
+ idx_all = np.arange(N)
+ if sub_n is None:
+ sub_n = max(3, (N + 1) // 2)
+ else:
+ sub_n = max(3, min(sub_n, N))
+
+ # Pre-alignment + default threshold
+ r0, t0, s0, pose_est0 = _umeyama_sim3_from_paths(pose_ref, pose_est)
+ if inlier_thresh is None:
+ inlier_thresh = _median_nn_thresh(pose_ref, pose_est0)
+
+ P_ref_all = pose_ref[:, :3, 3]
+
+ best_model = (r0, t0, s0)
+ best_inliers = None
+ best_score = (-1, np.inf) # (num_inliers, mean_err)
+
+ for _ in range(max_iters):
+ sample = rng.choice(idx_all, size=sub_n, replace=False)
+ try:
+ r, t, s, _ = _umeyama_sim3_from_paths(pose_ref[sample], pose_est[sample])
+ except Exception:
+ continue
+ pose_h = _apply_sim3_to_poses(pose_est, r, t, s)
+ P_h = pose_h[:, :3, 3]
+ errs = np.linalg.norm(P_h - P_ref_all, axis=1) # Match by same index
+ inliers = errs <= inlier_thresh
+ k = int(inliers.sum())
+ mean_err = float(errs[inliers].mean()) if k > 0 else np.inf
+ if (k > best_score[0]) or (k == best_score[0] and mean_err < best_score[1]):
+ best_score = (k, mean_err)
+ best_model = (r, t, s)
+ best_inliers = inliers
+
+ # Fit again with best inliers
+ if best_inliers is not None and best_inliers.sum() >= 3:
+ r, t, s, _ = _umeyama_sim3_from_paths(pose_ref[best_inliers], pose_est[best_inliers])
+ else:
+ r, t, s = best_model
+ return r, t, s
+
+
+def align_poses_umeyama(
+ ext_ref: np.ndarray,
+ ext_est: np.ndarray,
+ return_aligned=False,
+ ransac=False,
+ sub_n=None,
+ inlier_thresh=None,
+ ransac_max_iters=10,
+ random_state=None,
+):
+ """
+ Align estimated trajectory to reference using Umeyama Sim(3).
+ Default no RANSAC; if ransac=True, use RANSAC (max iterations default 10).
+ - sub_n defaults to half the number of frames (rounded up, at least 3)
+ - inlier_thresh defaults to median of "distance from each estimated pose to
+ nearest reference pose after pre-alignment"
+ Returns rotation (3x3), translation (3,), scale; optionally returns aligned extrinsics (4x4).
+ """
+ pose_ref, pose_est = _poses_from_ext(ext_ref, ext_est)
+
+ if not ransac:
+ r, t, s, pose_est_aligned = _umeyama_sim3_from_paths(pose_ref, pose_est)
+ else:
+ r, t, s = _ransac_align_sim3(
+ pose_ref,
+ pose_est,
+ sub_n=sub_n,
+ inlier_thresh=inlier_thresh,
+ max_iters=ransac_max_iters,
+ random_state=random_state,
+ )
+ pose_est_aligned = _apply_sim3_to_poses(pose_est, r, t, s)
+
+ if return_aligned:
+ ext_est_aligned = affine_inverse_np(pose_est_aligned)
+ return r, t, s, ext_est_aligned
+ return r, t, s
+
+
+# def align_poses_umeyama(ext_ref: np.ndarray, ext_est: np.ndarray, return_aligned=False):
+# """
+# Align estimated trajectory to reference trajectory using Umeyama Sim(3)
+# alignment (via evo PosePath3D). # noqa
+# Returns rotation, translation, and scale.
+# """
+# # If input extrinsics are 3x4, convert to 4x4 by padding
+# if ext_ref.shape[1] == 3:
+# ext_ref_ = np.eye(4)[None].repeat(len(ext_ref), 0)
+# ext_ref_[:, :3] = ext_ref
+# ext_ref = ext_ref_
+# if ext_est.shape[1] == 3:
+# ext_est_ = np.eye(4)[None].repeat(len(ext_est), 0)
+# ext_est_[:, :3] = ext_est
+# ext_est = ext_est_
+
+# # Convert to camera poses (inverse extrinsics)
+# pose_ref = affine_inverse_np(ext_ref)
+# pose_est = affine_inverse_np(ext_est)
+
+# # Create evo PosePath3D objects
+# path_ref = PosePath3D(poses_se3=pose_ref)
+# path_est = PosePath3D(poses_se3=pose_est)
+# r, t, s = path_est.align(path_ref, correct_scale=True)
+# if return_aligned:
+# return r, t, s, affine_inverse_np(np.stack(path_est.poses_se3))
+# else:
+# return r, t, s
+
+
+def apply_umeyama_alignment_to_ext(
+ rot: np.ndarray, # (3,3)
+ trans: np.ndarray, # (3,) or (1,3)
+ scale: float,
+ ext_est: np.ndarray, # (...,4,4) or (...,3,4)
+) -> np.ndarray:
+ """
+ Apply Sim(3) (R, t, s) to a batch of world-to-camera extrinsics ext_est.
+ Returns the aligned extrinsics, with the same shape as input.
+ """
+
+ # Allow 3x4 extrinsics: pad to 4x4
+ if ext_est.shape[-2:] == (3, 4):
+ pad = np.zeros((*ext_est.shape[:-2], 4, 4), dtype=ext_est.dtype)
+ pad[..., :3, :4] = ext_est
+ pad[..., 3, 3] = 1.0
+ ext_est = pad
+
+ # Convert world-to-camera to camera-to-world
+ pose_est = affine_inverse_np(ext_est) # (...,4,4)
+ R_e = pose_est[..., :3, :3] # (...,3,3)
+ t_e = pose_est[..., :3, 3] # (...,3)
+
+ # Apply Sim(3) transformation
+ R_a = np.einsum("ij,...jk->...ik", rot, R_e) # (...,3,3)
+ t_a = scale * np.einsum("ij,...j->...i", rot, t_e) + trans # (...,3)
+
+ # Assemble the transformed pose
+ pose_a = np.zeros_like(pose_est)
+ pose_a[..., :3, :3] = R_a
+ pose_a[..., :3, 3] = t_a
+ pose_a[..., 3, 3] = 1.0
+
+ # Convert back to world-to-camera
+ return affine_inverse_np(pose_a)
+
+
+def transform_points_sim3(points, rot, trans, scale, inverse=False):
+ """
+ Sim(3) transform point cloud
+ points: (N, 3)
+ rot: (3, 3)
+ trans: (3,) or (1, 3)
+ scale: float
+ inverse: Whether to do inverse transform (ref->est)
+ Returns: (N, 3)
+ """
+ if not inverse:
+ # Forward: est -> ref
+ return scale * (points @ rot.T) + trans
+ else:
+ # Inverse: ref -> est
+ return ((points - trans) @ rot) / scale
+
+
+def _rand_rot():
+ u1, u2, u3 = np.random.rand(3)
+ q = np.array(
+ [
+ np.sqrt(1 - u1) * np.sin(2 * np.math.pi * u2),
+ np.sqrt(1 - u1) * np.cos(2 * np.math.pi * u2),
+ np.sqrt(u1) * np.sin(2 * np.math.pi * u3),
+ np.sqrt(u1) * np.cos(2 * np.math.pi * u3),
+ ]
+ )
+ w, x, y, z = q
+ return np.array(
+ [
+ [1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)],
+ [2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)],
+ [2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)],
+ ]
+ )
+
+
+def _rand_pose():
+ R, t = _rand_rot(), np.random.randn(3)
+ P = np.eye(4)
+ P[:3, :3] = R
+ P[:3, 3] = t
+ return P
+
+
+if __name__ == "__main__":
+ np.random.seed(42)
+ # 1. Randomly generate reference trajectory and Sim(3)
+ N = 8
+ pose_ref = np.stack([_rand_pose() for _ in range(N)]) # (N,4,4) cam→world
+ rot_gt = _rand_rot()
+ scale_gt = 2.3
+ trans_gt = np.random.randn(3)
+ # 2. Generate estimated trajectory (apply Sim(3))
+ pose_est = np.zeros_like(pose_ref)
+ for i in range(N):
+ R = pose_ref[i][:3, :3]
+ t = pose_ref[i][:3, 3]
+ pose_est[i][:3, :3] = rot_gt @ R
+ pose_est[i][:3, 3] = scale_gt * (rot_gt @ t) + trans_gt
+ pose_est[i][3, 3] = 1.0
+ # 3. Get extrinsics (world->cam)
+ ext_ref = affine_inverse_np(pose_ref)
+ ext_est = affine_inverse_np(pose_est)
+ # 4. Use umeyama alignment, estimate Sim(3)
+ r_est, t_est, s_est = align_poses_umeyama(ext_ref, ext_est)
+ print("GT scale:", scale_gt, "Estimated:", s_est)
+ print("GT trans:", trans_gt, "Estimated:", t_est)
+ print("GT rot:\n", rot_gt, "\nEstimated:\n", r_est)
+ # 5. Random point cloud, in ref frame
+ num_points = 100
+ points_ref = np.random.randn(num_points, 3)
+ # 6. Use GT Sim(3) inverse transform to est frame
+ points_est = transform_points_sim3(points_ref, rot_gt, trans_gt, scale_gt, inverse=True)
+ # 7. Use estimated Sim(3) forward transform back to ref frame
+ points_ref_recovered = transform_points_sim3(points_est, r_est, t_est, s_est, inverse=False)
+ # 8. Check error
+ err = np.abs(points_ref_recovered - points_ref)
+ print("Point cloud sim3 transform error (mean abs):", err.mean())
+ print("Point cloud sim3 transform error (max abs):", err.max())
+ assert err.mean() < 1e-6, "Mean sim3 transform error too large!"
+ assert err.max() < 1e-5, "Max sim3 transform error too large!"
+ print("Sim(3) point cloud transform & alignment test passed!")
diff --git a/cameras/source/src/depth_anything_3/utils/ray_utils.py b/cameras/source/src/depth_anything_3/utils/ray_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..6244dc40089a7bfda7889ddc153d48c44f717da7
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/ray_utils.py
@@ -0,0 +1,523 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import torch
+from einops import repeat
+from .geometry import unproject_depth
+
+
+def compute_optimal_rotation_intrinsics_batch(
+ rays_origin, rays_target, z_threshold=1e-4, reproj_threshold=0.2, weights=None,
+ n_sample = None,
+ n_iter=100,
+ num_sample_for_ransac=8,
+ rand_sample_iters_idx=None,
+):
+ """
+ Args:
+ rays_origin (torch.Tensor): (B, N, 3)
+ rays_target (torch.Tensor): (B, N, 3)
+ z_threshold (float): Threshold for z value to be considered valid.
+
+ Returns:
+ R (torch.tensor): (3, 3)
+ focal_length (torch.tensor): (2,)
+ principal_point (torch.tensor): (2,)
+ """
+ device = rays_origin.device
+ B, N, _ = rays_origin.shape
+ z_mask = torch.logical_and(
+ torch.abs(rays_target[:, :, 2]) > z_threshold, torch.abs(rays_origin[:, :, 2]) > z_threshold
+ ) # (B, N, 1)
+ rays_origin = rays_origin.clone()
+ rays_target = rays_target.clone()
+ rays_origin[:, :, 0][z_mask] /= rays_origin[:, :, 2][z_mask]
+ rays_origin[:, :, 1][z_mask] /= rays_origin[:, :, 2][z_mask]
+ rays_target[:, :, 0][z_mask] /= rays_target[:, :, 2][z_mask]
+ rays_target[:, :, 1][z_mask] /= rays_target[:, :, 2][z_mask]
+
+ rays_origin = rays_origin[:, :, :2]
+ rays_target = rays_target[:, :, :2]
+ assert weights is not None, "weights must be provided"
+ weights[~z_mask] = 0
+
+ A_list = []
+ max_chunk_size = 2
+ for i in range(0, rays_origin.shape[0], max_chunk_size):
+ A = ransac_find_homography_weighted_fast_batch(
+ rays_origin[i:i+max_chunk_size],
+ rays_target[i:i+max_chunk_size],
+ weights[i:i+max_chunk_size],
+ n_iter=n_iter,
+ n_sample = n_sample,
+ num_sample_for_ransac=num_sample_for_ransac,
+ reproj_threshold=reproj_threshold,
+ rand_sample_iters_idx=rand_sample_iters_idx,
+ max_inlier_num=8000,
+ )
+ A = A.to(device)
+ A_need_inv_mask = torch.linalg.det(A) < 0
+ A[A_need_inv_mask] = -A[A_need_inv_mask]
+ A_list.append(A)
+
+ A = torch.cat(A_list, dim=0)
+
+ R_list = []
+ f_list = []
+ pp_list = []
+ for i in range(A.shape[0]):
+ R, L = ql_decomposition(A[i])
+ L = L / L[2][2]
+
+ f = torch.stack((L[0][0], L[1][1]))
+ pp = torch.stack((L[2][0], L[2][1]))
+ R_list.append(R)
+ f_list.append(f)
+ pp_list.append(pp)
+
+ R = torch.stack(R_list)
+ f = torch.stack(f_list)
+ pp = torch.stack(pp_list)
+
+ return R, f, pp
+
+
+# https://www.reddit.com/r/learnmath/comments/v1crd7/linear_algebra_qr_to_ql_decomposition/
+def ql_decomposition(A):
+ P = torch.tensor([[0, 0, 1], [0, 1, 0], [1, 0, 0]], device=A.device).float()
+ A_tilde = torch.matmul(A, P)
+ Q_tilde, R_tilde = torch.linalg.qr(A_tilde)
+ Q = torch.matmul(Q_tilde, P)
+ L = torch.matmul(torch.matmul(P, R_tilde), P)
+ d = torch.diag(L)
+ Q[:, 0] *= torch.sign(d[0])
+ Q[:, 1] *= torch.sign(d[1])
+ Q[:, 2] *= torch.sign(d[2])
+ L[0] *= torch.sign(d[0])
+ L[1] *= torch.sign(d[1])
+ L[2] *= torch.sign(d[2])
+ return Q, L
+
+def find_homography_least_squares_weighted_torch(src_pts, dst_pts, confident_weight):
+ """
+ src_pts: (N,2) source points (torch.Tensor, float32/float64)
+ dst_pts: (N,2) target points (torch.Tensor, float32/float64)
+ confident_weight: (N,) weights (torch.Tensor)
+ Returns: (3,3) homography matrix H (torch.Tensor)
+ """
+ assert src_pts.shape == dst_pts.shape
+ N = src_pts.shape[0]
+ if N < 4:
+ raise ValueError("At least 4 points are required to compute homography.")
+ assert confident_weight.shape == (N,)
+
+ w = confident_weight.sqrt().unsqueeze(1) # (N,1)
+
+ x = src_pts[:, 0:1] # (N,1)
+ y = src_pts[:, 1:2] # (N,1)
+ u = dst_pts[:, 0:1]
+ v = dst_pts[:, 1:2]
+
+ zeros = torch.zeros_like(x)
+
+ # Construct A matrix (2N, 9)
+ A1 = torch.cat([-x * w, -y * w, -w, zeros, zeros, zeros, x * u * w, y * u * w, u * w], dim=1)
+ A2 = torch.cat([zeros, zeros, zeros, -x * w, -y * w, -w, x * v * w, y * v * w, v * w], dim=1)
+ A = torch.cat([A1, A2], dim=0) # (2N, 9)
+
+ # SVD
+ # Note: torch.linalg.svd returns U, S, Vh, where Vh is the transpose of V
+ _, _, Vh = torch.linalg.svd(A)
+ H = Vh[-1].reshape(3, 3)
+ H = H / H[-1, -1]
+ return H
+
+
+def ransac_find_homography_weighted(
+ src_pts,
+ dst_pts,
+ confident_weight,
+ n_iter=100,
+ sample_ratio=0.2,
+ reproj_threshold=3.0,
+ num_sample_for_ransac=16,
+ random_seed=None,
+):
+ """
+ RANSAC version of weighted Homography estimation.
+ Sample 4 points from the top 50% weighted points each time.
+ reproj_threshold: points with reprojection error less than this value are inliers
+ Returns: best_H
+ """
+ if random_seed is not None:
+ torch.manual_seed(random_seed)
+ N = src_pts.shape[0]
+ assert N >= 4
+ # 1. Select top 50% weighted points
+ sorted_idx = torch.argsort(confident_weight, descending=True)
+ n_sample = max(num_sample_for_ransac, int(N * sample_ratio))
+ candidate_idx = sorted_idx[:n_sample]
+ best_inlier_mask = None
+ best_score = 0
+ for _ in range(n_iter):
+ # 2. Randomly sample 4 points
+ idx = candidate_idx[torch.randperm(n_sample)[:num_sample_for_ransac]]
+ # 3. Compute Homography
+ try:
+ H = find_homography_least_squares_weighted_torch(
+ src_pts[idx], dst_pts[idx], confident_weight[idx]
+ )
+ except Exception:
+ H = torch.eye(3, dtype=src_pts.dtype, device=src_pts.device)
+ # 4. Compute reprojection error for all points
+ src_homo = torch.cat(
+ [src_pts, torch.ones(N, 1, dtype=src_pts.dtype, device=src_pts.device)], dim=1
+ )
+ proj = (H @ src_homo.T).T
+ proj = proj[:, :2] / proj[:, 2:3]
+ error = ((proj - dst_pts) ** 2).sum(dim=1).sqrt() # Euclidean distance
+ inlier_mask = error < reproj_threshold
+ total_score = (inlier_mask * confident_weight).sum().item()
+ n_inlier = inlier_mask.sum().item()
+ if n_inlier < 4:
+ continue # At least 4 inliers required for fitting
+
+ if total_score > best_score:
+ best_score = total_score
+ best_inlier_mask = inlier_mask
+
+ # 5. Refit Homography using inliers
+ H_inlier = find_homography_least_squares_weighted_torch(
+ src_pts[best_inlier_mask], dst_pts[best_inlier_mask], confident_weight[best_inlier_mask]
+ )
+
+ return H_inlier
+
+
+def find_homography_least_squares_weighted_torch_batch(
+ src_pts_batch, dst_pts_batch, confident_weight_batch
+):
+ """
+ Batch version of weighted least squares Homography
+ src_pts_batch: (B, K, 2)
+ dst_pts_batch: (B, K, 2)
+ confident_weight_batch: (B, K)
+ Returns: (B, 3, 3)
+ """
+ B, K, _ = src_pts_batch.shape
+ w = confident_weight_batch.sqrt().unsqueeze(2) # (B,K,1)
+ x = src_pts_batch[:, :, 0:1]
+ y = src_pts_batch[:, :, 1:2]
+ u = dst_pts_batch[:, :, 0:1]
+ v = dst_pts_batch[:, :, 1:2]
+ zeros = torch.zeros_like(x)
+ A1 = torch.cat([-x * w, -y * w, -w, zeros, zeros, zeros, x * u * w, y * u * w, u * w], dim=2)
+ A2 = torch.cat([zeros, zeros, zeros, -x * w, -y * w, -w, x * v * w, y * v * w, v * w], dim=2)
+ A = torch.cat([A1, A2], dim=1) # (B, 2K, 9)
+ # SVD: torch.linalg.svd supports batch
+ _, _, Vh = torch.linalg.svd(A)
+ H = Vh[:, -1].reshape(B, 3, 3)
+ H = H / H[:, 2:3, 2:3]
+ return H
+
+
+def ransac_find_homography_weighted_fast(
+ src_pts,
+ dst_pts,
+ confident_weight,
+ n_sample,
+ n_iter=100,
+ reproj_threshold=3.0,
+ num_sample_for_ransac=8,
+ random_seed=None,
+ rand_sample_iters_idx=None,
+):
+ """
+ Batch version of RANSAC weighted Homography estimation.
+ Returns: H_inlier
+ """
+ if random_seed is not None:
+ torch.manual_seed(random_seed)
+ N = src_pts.shape[0]
+ device = src_pts.device
+ assert N >= 4
+ # 1. Select top weighted points by sample_ratio
+ sorted_idx = torch.argsort(confident_weight, descending=True)
+ candidate_idx = sorted_idx[:n_sample] # (n_sample,)
+ if rand_sample_iters_idx is None:
+ rand_sample_iters_idx = torch.stack(
+ [torch.randperm(n_sample, device=device)[:num_sample_for_ransac] for _ in range(n_iter)],
+ dim=0,
+ ) # (n_iter, num_sample_for_ransac)
+ # 2. Generate all sampling groups at once
+ # shape: (n_iter, num_sample_for_ransac)
+ rand_idx = candidate_idx[rand_sample_iters_idx] # (n_iter, num_sample_for_ransac)
+ # 3. Construct batch input
+ src_pts_batch = src_pts[rand_idx] # (n_iter, num_sample_for_ransac, 2)
+ dst_pts_batch = dst_pts[rand_idx] # (n_iter, num_sample_for_ransac, 2)
+ confident_weight_batch = confident_weight[rand_idx] # (n_iter, num_sample_for_ransac)
+ # 4. Batch fit Homography
+ H_batch = find_homography_least_squares_weighted_torch_batch(
+ src_pts_batch, dst_pts_batch, confident_weight_batch
+ ) # (n_iter, 3, 3)
+ # 5. Batch evaluate inliers for all H
+ src_homo = torch.cat(
+ [src_pts, torch.ones(N, 1, dtype=src_pts.dtype, device=src_pts.device)], dim=1
+ ) # (N,3)
+ src_homo_expand = src_homo.unsqueeze(0).expand(n_iter, N, 3) # (n_iter, N, 3)
+ dst_pts_expand = dst_pts.unsqueeze(0).expand(n_iter, N, 2) # (n_iter, N, 2)
+ confident_weight_expand = confident_weight.unsqueeze(0).expand(n_iter, N) # (n_iter, N)
+ # H_batch: (n_iter, 3, 3)
+ proj = torch.bmm(src_homo_expand, H_batch.transpose(1, 2)) # (n_iter, N, 3)
+ proj_xy = proj[:, :, :2] / proj[:, :, 2:3] # (n_iter, N, 2)
+ error = ((proj_xy - dst_pts_expand) ** 2).sum(dim=2).sqrt() # (n_iter, N)
+ inlier_mask = error < reproj_threshold # (n_iter, N)
+ total_score = (inlier_mask * confident_weight_expand).sum(dim=1) # (n_iter,)
+ # 6. Select the sampling group with the highest score
+ best_idx = torch.argmax(total_score)
+ best_inlier_mask = inlier_mask[best_idx] # (N,)
+ inlier_src_pts = src_pts[best_inlier_mask]
+ inlier_dst_pts = dst_pts[best_inlier_mask]
+ inlier_confident_weight = confident_weight[best_inlier_mask]
+
+ max_inlier_num = 10000
+ sorted_idx = torch.argsort(inlier_confident_weight, descending=True)
+
+ # method 1: sort according to confident_weight, and only keep max_inlier_num pts
+ # sorted_idx = sorted_idx[:max_inlier_num]
+
+ # method 2: random choose max_inlier_num pts
+ sorted_idx = sorted_idx[torch.randperm(len(sorted_idx))[:max_inlier_num]]
+
+ inlier_src_pts = inlier_src_pts[sorted_idx]
+ inlier_dst_pts = inlier_dst_pts[sorted_idx]
+ inlier_confident_weight = inlier_confident_weight[sorted_idx]
+ # 7. Refit Homography using inliers
+ H_inlier = find_homography_least_squares_weighted_torch(
+ inlier_src_pts, inlier_dst_pts, inlier_confident_weight
+ )
+ return H_inlier
+
+
+def ransac_find_homography_weighted_fast_batch(
+ src_pts, # (B, N, 3)
+ dst_pts, # (B, N, 2)
+ confident_weight, # (B, N)
+ n_sample,
+ n_iter=100,
+ reproj_threshold=3.0,
+ num_sample_for_ransac=8,
+ max_inlier_num=10000,
+ random_seed=None,
+ rand_sample_iters_idx=None,
+):
+ """
+ Batch version of RANSAC weighted Homography estimation (supports batch).
+ Input:
+ src_pts: (B, N, 2)
+ dst_pts: (B, N, 2)
+ confident_weight: (B, N)
+ Returns:
+ H_inlier: (B, 3, 3)
+ """
+ if random_seed is not None:
+ torch.manual_seed(random_seed)
+ B, N, _ = src_pts.shape
+ assert N >= 4
+
+ device = src_pts.device
+
+ # 1. Select top weighted points by sample_ratio
+ sorted_idx = torch.argsort(confident_weight, descending=True, dim=1) # (B, N)
+ candidate_idx = sorted_idx[:, :n_sample] # (B, n_sample)
+
+ # 2. Generate all sampling groups at once
+ # rand_idx: (B, n_iter, num_sample_for_ransac)
+ if rand_sample_iters_idx is None:
+ rand_sample_iters_idx = torch.stack(
+ [torch.randperm(n_sample, device=device)[:num_sample_for_ransac] for _ in range(n_iter)],
+ dim=0,
+ ) # (n_iter, num_sample_for_ransac)
+
+ rand_idx = candidate_idx[:, rand_sample_iters_idx] # (B, n_iter, num_sample_for_ransac)
+
+ # 3. Construct batch input
+ # Indexing method below: (B, n_iter, num_sample_for_ransac, ...)
+ b_idx = torch.arange(B, device=device).view(B, 1, 1).expand(B, n_iter, num_sample_for_ransac)
+ src_pts_batch = src_pts[b_idx, rand_idx] # (B, n_iter, num_sample_for_ransac, 2)
+ dst_pts_batch = dst_pts[b_idx, rand_idx] # (B, n_iter, num_sample_for_ransac, 2)
+ confident_weight_batch = confident_weight[b_idx, rand_idx] # (B, n_iter, num_sample_for_ransac)
+
+ # 4. Batch fit Homography
+ # Need to implement batch version that supports (B, n_iter, num_sample_for_ransac, ...) input
+ # Output H_batch: (B, n_iter, 3, 3)
+ cB, cN = src_pts_batch.shape[:2]
+ H_batch = find_homography_least_squares_weighted_torch_batch(
+ src_pts_batch.flatten(0, 1), dst_pts_batch.flatten(0, 1), confident_weight_batch.flatten(0, 1)
+ ) # (B, n_iter, 3, 3)
+ H_batch = H_batch.unflatten(0, (cB, cN))
+
+ # 5. Batch evaluate inliers for all H
+ src_homo = torch.cat(
+ [src_pts, torch.ones(B, N, 1, dtype=src_pts.dtype, device=src_pts.device)], dim=2
+ ) # (B, N, 3)
+ src_homo_expand = src_homo.unsqueeze(1).expand(B, n_iter, N, 3) # (B, n_iter, N, 3)
+ dst_pts_expand = dst_pts.unsqueeze(1).expand(B, n_iter, N, 2) # (B, n_iter, N, 2)
+ confident_weight_expand = confident_weight.unsqueeze(1).expand(B, n_iter, N) # (B, n_iter, N)
+
+ # H_batch: (B, n_iter, 3, 3)
+ # Need to reshape H_batch to (B*n_iter, 3, 3), src_homo_expand to (B*n_iter, N, 3)
+ H_batch_flat = H_batch.reshape(-1, 3, 3)
+ src_homo_expand_flat = src_homo_expand.reshape(-1, N, 3)
+ proj = torch.bmm(src_homo_expand_flat, H_batch_flat.transpose(1, 2)) # (B*n_iter, N, 3)
+ proj_xy = proj[:, :, :2] / proj[:, :, 2:3] # (B*n_iter, N, 2)
+ proj_xy = proj_xy.reshape(B, n_iter, N, 2)
+ error = ((proj_xy - dst_pts_expand) ** 2).sum(dim=3).sqrt() # (B, n_iter, N)
+ inlier_mask = error < reproj_threshold # (B, n_iter, N)
+ total_score = (inlier_mask * confident_weight_expand).sum(dim=2) # (B, n_iter)
+
+ # 6. Select the sampling group with the highest score
+ best_idx = torch.argmax(total_score, dim=1) # (B,)
+ best_inlier_mask = inlier_mask[torch.arange(B, device=device), best_idx] # (B, N)
+
+ # 7. Refit Homography using inliers
+ H_inlier_list = []
+ for b in range(B):
+ mask = best_inlier_mask[b]
+ inlier_src_pts = src_pts[b][mask] # (?, 3)
+ inlier_dst_pts = dst_pts[b][mask] # (?, 2)
+ inlier_confident_weight = confident_weight[b][mask] # (?)
+
+ sorted_idx = torch.argsort(inlier_confident_weight, descending=True)
+ # # method 1: sort according to confident_weight, and only keep max_inlier_num pts
+ # sorted_idx = sorted_idx[:max_inlier_num]
+ # method 2: random choose max_inlier_num pts
+ if len(sorted_idx) > max_inlier_num:
+ # random choose from first 95% confident pts
+ keep_len = max(int(len(sorted_idx) * 0.95), max_inlier_num)
+ sorted_idx = sorted_idx[:keep_len]
+ perm = torch.randperm(len(sorted_idx), device=device)[:max_inlier_num]
+ sorted_idx = sorted_idx[perm]
+ inlier_src_pts = inlier_src_pts[sorted_idx]
+ inlier_dst_pts = inlier_dst_pts[sorted_idx]
+ inlier_confident_weight = inlier_confident_weight[sorted_idx]
+
+ H_inlier = find_homography_least_squares_weighted_torch(
+ inlier_src_pts, inlier_dst_pts, inlier_confident_weight
+ ) # (3, 3)
+ H_inlier_list.append(H_inlier)
+ H_inlier = torch.stack(H_inlier_list, dim=0) # (B, 3, 3)
+ return H_inlier
+
+def get_params_for_ransac(N, device):
+ n_iter=100
+ sample_ratio=0.3
+ num_sample_for_ransac=8
+ n_sample = max(num_sample_for_ransac, int(N * sample_ratio))
+ rand_sample_iters_idx = torch.stack(
+ [torch.randperm(n_sample, device=device)[:num_sample_for_ransac] for _ in range(n_iter)],
+ dim=0,
+ ) # (n_iter, num_sample_for_ransac)
+ return n_iter, num_sample_for_ransac, n_sample, rand_sample_iters_idx
+
+
+def camray_to_caminfo(camray, confidence=None, reproj_threshold=0.2, training=False):
+ """
+ Args:
+ camray: (B, S, num_patches_y, num_patches_x, 6)
+ confidence: (B, S, num_patches_y, num_patches_x)
+ Returns:
+ R: (B, S, 3, 3)
+ T: (B, S, 3)
+ focal_lengths: (B, S, 2)
+ principal_points: (B, S, 2)
+ """
+ if confidence is None:
+ confidence = torch.ones_like(camray[:, :, :, :, 0])
+ B, S, num_patches_y, num_patches_x, _ = camray.shape
+ # identity K, assume imw=imh=2.0
+ I_K = torch.eye(3, dtype=camray.dtype, device=camray.device)
+ I_K[0, 2] = 1.0
+ I_K[1, 2] = 1.0
+ # repeat I_K to match camray
+ I_K = I_K.unsqueeze(0).unsqueeze(0).expand(B, S, -1, -1)
+
+ cam_plane_depth = torch.ones(
+ B, S, num_patches_y, num_patches_x, 1, dtype=camray.dtype, device=camray.device
+ )
+ I_cam_plane_unproj = unproject_depth(
+ cam_plane_depth,
+ I_K,
+ c2w=None,
+ ixt_normalized=True,
+ num_patches_x=num_patches_x,
+ num_patches_y=num_patches_y,
+ ) # (B, S, num_patches_y, num_patches_x, 3)
+
+ camray = camray.flatten(0, 1).flatten(1, 2) # (B*S, num_patches_y*num_patches_x, 6)
+ I_cam_plane_unproj = I_cam_plane_unproj.flatten(0, 1).flatten(
+ 1, 2
+ ) # (B*S, num_patches_y*num_patches_x, 3)
+ confidence = confidence.flatten(0, 1).flatten(1, 2) # (B*S, num_patches_y*num_patches_x)
+
+ # Compute optimal rotation to align rays
+ N = camray.shape[-2]
+ device = camray.device
+ n_iter, num_sample_for_ransac, n_sample, rand_sample_iters_idx = get_params_for_ransac(N, device)
+
+ # Use batch processing (confidence is guaranteed to be not None at this point)
+ if training:
+ camray = camray.clone().detach()
+ I_cam_plane_unproj = I_cam_plane_unproj.clone().detach()
+ confidence = confidence.clone().detach()
+ R, focal_lengths, principal_points = compute_optimal_rotation_intrinsics_batch(
+ I_cam_plane_unproj,
+ camray[:, :, :3],
+ reproj_threshold=reproj_threshold,
+ weights=confidence,
+ n_sample = n_sample,
+ n_iter=n_iter,
+ num_sample_for_ransac=num_sample_for_ransac,
+ rand_sample_iters_idx=rand_sample_iters_idx,
+ )
+
+ T = torch.sum(camray[:, :, 3:] * confidence.unsqueeze(-1), dim=1) / torch.sum(
+ confidence, dim=-1, keepdim=True
+ )
+
+ R = R.reshape(B, S, 3, 3)
+ T = T.reshape(B, S, 3)
+ focal_lengths = focal_lengths.reshape(B, S, 2)
+ principal_points = principal_points.reshape(B, S, 2)
+
+ return R, T, 1.0 / focal_lengths, principal_points + 1.0
+
+def get_extrinsic_from_camray(camray, conf, patch_size_y, patch_size_x, training=False):
+ pred_R, pred_T, pred_focal_lengths, pred_principal_points = camray_to_caminfo(
+ camray, confidence=conf.squeeze(-1), training=training
+ )
+
+ pred_extrinsic = torch.cat(
+ [
+ torch.cat([pred_R, pred_T.unsqueeze(-1)], dim=-1),
+ repeat(
+ torch.tensor([0, 0, 0, 1], dtype=pred_R.dtype, device=pred_R.device),
+ "c -> b s 1 c",
+ b=pred_R.shape[0],
+ s=pred_R.shape[1],
+ ),
+ ],
+ dim=-2,
+ ) # B, S, 4, 4
+ return pred_extrinsic, pred_focal_lengths, pred_principal_points
\ No newline at end of file
diff --git a/cameras/source/src/depth_anything_3/utils/read_write_model.py b/cameras/source/src/depth_anything_3/utils/read_write_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b4cf197f609665e46f14e6b3e8b6657efc5660c
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/read_write_model.py
@@ -0,0 +1,585 @@
+# Copyright (c), ETH Zurich and UNC Chapel Hill.
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+# All rights reserved.
+#
+# This file has been modified by ByteDance Ltd. and/or its affiliates. on 11/05/2025
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of
+# its contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+import argparse
+import collections
+import os
+import struct
+import numpy as np
+
+CameraModel = collections.namedtuple("CameraModel", ["model_id", "model_name", "num_params"])
+Camera = collections.namedtuple("Camera", ["id", "model", "width", "height", "params"])
+BaseImage = collections.namedtuple(
+ "Image", ["id", "qvec", "tvec", "camera_id", "name", "xys", "point3D_ids"]
+)
+Point3D = collections.namedtuple(
+ "Point3D", ["id", "xyz", "rgb", "error", "image_ids", "point2D_idxs"]
+)
+
+
+class Image(BaseImage):
+ def qvec2rotmat(self):
+ return qvec2rotmat(self.qvec)
+
+
+CAMERA_MODELS = {
+ CameraModel(model_id=0, model_name="SIMPLE_PINHOLE", num_params=3),
+ CameraModel(model_id=1, model_name="PINHOLE", num_params=4),
+ CameraModel(model_id=2, model_name="SIMPLE_RADIAL", num_params=4),
+ CameraModel(model_id=3, model_name="RADIAL", num_params=5),
+ CameraModel(model_id=4, model_name="OPENCV", num_params=8),
+ CameraModel(model_id=5, model_name="OPENCV_FISHEYE", num_params=8),
+ CameraModel(model_id=6, model_name="FULL_OPENCV", num_params=12),
+ CameraModel(model_id=7, model_name="FOV", num_params=5),
+ CameraModel(model_id=8, model_name="SIMPLE_RADIAL_FISHEYE", num_params=4),
+ CameraModel(model_id=9, model_name="RADIAL_FISHEYE", num_params=5),
+ CameraModel(model_id=10, model_name="THIN_PRISM_FISHEYE", num_params=12),
+}
+CAMERA_MODEL_IDS = {camera_model.model_id: camera_model for camera_model in CAMERA_MODELS}
+CAMERA_MODEL_NAMES = {camera_model.model_name: camera_model for camera_model in CAMERA_MODELS}
+
+
+def read_next_bytes(fid, num_bytes, format_char_sequence, endian_character="<"):
+ """Read and unpack the next bytes from a binary file.
+ :param fid:
+ :param num_bytes: Sum of combination of {2, 4, 8}, e.g. 2, 6, 16, 30, etc.
+ :param format_char_sequence: List of {c, e, f, d, h, H, i, I, l, L, q, Q}.
+ :param endian_character: Any of {@, =, <, >, !}
+ :return: Tuple of read and unpacked values.
+ """
+ data = fid.read(num_bytes)
+ return struct.unpack(endian_character + format_char_sequence, data)
+
+
+def write_next_bytes(fid, data, format_char_sequence, endian_character="<"):
+ """pack and write to a binary file.
+ :param fid:
+ :param data: data to send, if multiple elements are sent at the same time,
+ they should be encapsuled either in a list or a tuple
+ :param format_char_sequence: List of {c, e, f, d, h, H, i, I, l, L, q, Q}.
+ should be the same length as the data list or tuple
+ :param endian_character: Any of {@, =, <, >, !}
+ """
+ if isinstance(data, (list, tuple)):
+ bytes = struct.pack(endian_character + format_char_sequence, *data)
+ else:
+ bytes = struct.pack(endian_character + format_char_sequence, data)
+ fid.write(bytes)
+
+
+def read_cameras_text(path):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::WriteCamerasText(const std::string& path)
+ void Reconstruction::ReadCamerasText(const std::string& path)
+ """
+ cameras = {}
+ with open(path) as fid:
+ while True:
+ line = fid.readline()
+ if not line:
+ break
+ line = line.strip()
+ if len(line) > 0 and line[0] != "#":
+ elems = line.split()
+ camera_id = int(elems[0])
+ model = elems[1]
+ width = int(elems[2])
+ height = int(elems[3])
+ params = np.array(tuple(map(float, elems[4:])))
+ cameras[camera_id] = Camera(
+ id=camera_id,
+ model=model,
+ width=width,
+ height=height,
+ params=params,
+ )
+ return cameras
+
+
+def read_cameras_binary(path_to_model_file):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::WriteCamerasBinary(const std::string& path)
+ void Reconstruction::ReadCamerasBinary(const std::string& path)
+ """
+ cameras = {}
+ with open(path_to_model_file, "rb") as fid:
+ num_cameras = read_next_bytes(fid, 8, "Q")[0]
+ for _ in range(num_cameras):
+ camera_properties = read_next_bytes(fid, num_bytes=24, format_char_sequence="iiQQ")
+ camera_id = camera_properties[0]
+ model_id = camera_properties[1]
+ model_name = CAMERA_MODEL_IDS[camera_properties[1]].model_name
+ width = camera_properties[2]
+ height = camera_properties[3]
+ num_params = CAMERA_MODEL_IDS[model_id].num_params
+ params = read_next_bytes(
+ fid,
+ num_bytes=8 * num_params,
+ format_char_sequence="d" * num_params,
+ )
+ cameras[camera_id] = Camera(
+ id=camera_id,
+ model=model_name,
+ width=width,
+ height=height,
+ params=np.array(params),
+ )
+ assert len(cameras) == num_cameras
+ return cameras
+
+
+def write_cameras_text(cameras, path):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::WriteCamerasText(const std::string& path)
+ void Reconstruction::ReadCamerasText(const std::string& path)
+ """
+ HEADER = (
+ "# Camera list with one line of data per camera:\n"
+ + "# CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[]\n"
+ + f"# Number of cameras: {len(cameras)}\n"
+ )
+ with open(path, "w") as fid:
+ fid.write(HEADER)
+ for _, cam in cameras.items():
+ to_write = [cam.id, cam.model, cam.width, cam.height, *cam.params]
+ line = " ".join([str(elem) for elem in to_write])
+ fid.write(line + "\n")
+
+
+def write_cameras_binary(cameras, path_to_model_file):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::WriteCamerasBinary(const std::string& path)
+ void Reconstruction::ReadCamerasBinary(const std::string& path)
+ """
+ with open(path_to_model_file, "wb") as fid:
+ write_next_bytes(fid, len(cameras), "Q")
+ for _, cam in cameras.items():
+ model_id = CAMERA_MODEL_NAMES[cam.model].model_id
+ camera_properties = [cam.id, model_id, cam.width, cam.height]
+ write_next_bytes(fid, camera_properties, "iiQQ")
+ for p in cam.params:
+ write_next_bytes(fid, float(p), "d")
+ return cameras
+
+
+def read_images_text(path):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::ReadImagesText(const std::string& path)
+ void Reconstruction::WriteImagesText(const std::string& path)
+ """
+ images = {}
+ with open(path) as fid:
+ while True:
+ line = fid.readline()
+ if not line:
+ break
+ line = line.strip()
+ if len(line) > 0 and line[0] != "#":
+ elems = line.split()
+ image_id = int(elems[0])
+ qvec = np.array(tuple(map(float, elems[1:5])))
+ tvec = np.array(tuple(map(float, elems[5:8])))
+ camera_id = int(elems[8])
+ image_name = elems[9]
+ elems = fid.readline().split()
+ xys = np.column_stack(
+ [
+ tuple(map(float, elems[0::3])),
+ tuple(map(float, elems[1::3])),
+ ]
+ )
+ point3D_ids = np.array(tuple(map(int, elems[2::3])))
+ images[image_id] = Image(
+ id=image_id,
+ qvec=qvec,
+ tvec=tvec,
+ camera_id=camera_id,
+ name=image_name,
+ xys=xys,
+ point3D_ids=point3D_ids,
+ )
+ return images
+
+
+def read_images_binary(path_to_model_file):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::ReadImagesBinary(const std::string& path)
+ void Reconstruction::WriteImagesBinary(const std::string& path)
+ """
+ images = {}
+ with open(path_to_model_file, "rb") as fid:
+ num_reg_images = read_next_bytes(fid, 8, "Q")[0]
+ for _ in range(num_reg_images):
+ binary_image_properties = read_next_bytes(
+ fid, num_bytes=64, format_char_sequence="idddddddi"
+ )
+ image_id = binary_image_properties[0]
+ qvec = np.array(binary_image_properties[1:5])
+ tvec = np.array(binary_image_properties[5:8])
+ camera_id = binary_image_properties[8]
+ binary_image_name = b""
+ current_char = read_next_bytes(fid, 1, "c")[0]
+ while current_char != b"\x00": # look for the ASCII 0 entry
+ binary_image_name += current_char
+ current_char = read_next_bytes(fid, 1, "c")[0]
+ image_name = binary_image_name.decode("utf-8")
+ num_points2D = read_next_bytes(fid, num_bytes=8, format_char_sequence="Q")[0]
+ x_y_id_s = read_next_bytes(
+ fid,
+ num_bytes=24 * num_points2D,
+ format_char_sequence="ddq" * num_points2D,
+ )
+ xys = np.column_stack(
+ [
+ tuple(map(float, x_y_id_s[0::3])),
+ tuple(map(float, x_y_id_s[1::3])),
+ ]
+ )
+ point3D_ids = np.array(tuple(map(int, x_y_id_s[2::3])))
+ images[image_id] = Image(
+ id=image_id,
+ qvec=qvec,
+ tvec=tvec,
+ camera_id=camera_id,
+ name=image_name,
+ xys=xys,
+ point3D_ids=point3D_ids,
+ )
+ return images
+
+
+def write_images_text(images, path):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::ReadImagesText(const std::string& path)
+ void Reconstruction::WriteImagesText(const std::string& path)
+ """
+ if len(images) == 0:
+ mean_observations = 0
+ else:
+ mean_observations = sum((len(img.point3D_ids) for _, img in images.items())) / len(images)
+ HEADER = (
+ "# Image list with two lines of data per image:\n"
+ + "# IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME\n"
+ + "# POINTS2D[] as (X, Y, POINT3D_ID)\n"
+ + "# Number of images: {}, mean observations per image: {}\n".format(
+ len(images), mean_observations
+ )
+ )
+
+ with open(path, "w") as fid:
+ fid.write(HEADER)
+ for _, img in images.items():
+ image_header = [
+ img.id,
+ *img.qvec,
+ *img.tvec,
+ img.camera_id,
+ img.name,
+ ]
+ first_line = " ".join(map(str, image_header))
+ fid.write(first_line + "\n")
+
+ points_strings = []
+ for xy, point3D_id in zip(img.xys, img.point3D_ids):
+ points_strings.append(" ".join(map(str, [*xy, point3D_id])))
+ fid.write(" ".join(points_strings) + "\n")
+
+
+def write_images_binary(images, path_to_model_file):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::ReadImagesBinary(const std::string& path)
+ void Reconstruction::WriteImagesBinary(const std::string& path)
+ """
+ with open(path_to_model_file, "wb") as fid:
+ write_next_bytes(fid, len(images), "Q")
+ for _, img in images.items():
+ write_next_bytes(fid, img.id, "i")
+ write_next_bytes(fid, img.qvec.tolist(), "dddd")
+ write_next_bytes(fid, img.tvec.tolist(), "ddd")
+ write_next_bytes(fid, img.camera_id, "i")
+ for char in img.name:
+ write_next_bytes(fid, char.encode("utf-8"), "c")
+ write_next_bytes(fid, b"\x00", "c")
+ write_next_bytes(fid, len(img.point3D_ids), "Q")
+ for xy, p3d_id in zip(img.xys, img.point3D_ids):
+ write_next_bytes(fid, [*xy, p3d_id], "ddq")
+
+
+def read_points3D_text(path):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::ReadPoints3DText(const std::string& path)
+ void Reconstruction::WritePoints3DText(const std::string& path)
+ """
+ points3D = {}
+ with open(path) as fid:
+ while True:
+ line = fid.readline()
+ if not line:
+ break
+ line = line.strip()
+ if len(line) > 0 and line[0] != "#":
+ elems = line.split()
+ point3D_id = int(elems[0])
+ xyz = np.array(tuple(map(float, elems[1:4])))
+ rgb = np.array(tuple(map(int, elems[4:7])))
+ error = float(elems[7])
+ image_ids = np.array(tuple(map(int, elems[8::2])))
+ point2D_idxs = np.array(tuple(map(int, elems[9::2])))
+ points3D[point3D_id] = Point3D(
+ id=point3D_id,
+ xyz=xyz,
+ rgb=rgb,
+ error=error,
+ image_ids=image_ids,
+ point2D_idxs=point2D_idxs,
+ )
+ return points3D
+
+
+def read_points3D_binary(path_to_model_file):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::ReadPoints3DBinary(const std::string& path)
+ void Reconstruction::WritePoints3DBinary(const std::string& path)
+ """
+ points3D = {}
+ with open(path_to_model_file, "rb") as fid:
+ num_points = read_next_bytes(fid, 8, "Q")[0]
+ for _ in range(num_points):
+ binary_point_line_properties = read_next_bytes(
+ fid, num_bytes=43, format_char_sequence="QdddBBBd"
+ )
+ point3D_id = binary_point_line_properties[0]
+ xyz = np.array(binary_point_line_properties[1:4])
+ rgb = np.array(binary_point_line_properties[4:7])
+ error = np.array(binary_point_line_properties[7])
+ track_length = read_next_bytes(fid, num_bytes=8, format_char_sequence="Q")[0]
+ track_elems = read_next_bytes(
+ fid,
+ num_bytes=8 * track_length,
+ format_char_sequence="ii" * track_length,
+ )
+ image_ids = np.array(tuple(map(int, track_elems[0::2])))
+ point2D_idxs = np.array(tuple(map(int, track_elems[1::2])))
+ points3D[point3D_id] = Point3D(
+ id=point3D_id,
+ xyz=xyz,
+ rgb=rgb,
+ error=error,
+ image_ids=image_ids,
+ point2D_idxs=point2D_idxs,
+ )
+ return points3D
+
+
+def write_points3D_text(points3D, path):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::ReadPoints3DText(const std::string& path)
+ void Reconstruction::WritePoints3DText(const std::string& path)
+ """
+ if len(points3D) == 0:
+ mean_track_length = 0
+ else:
+ mean_track_length = sum((len(pt.image_ids) for _, pt in points3D.items())) / len(points3D)
+ HEADER = (
+ "# 3D point list with one line of data per point:\n"
+ + "# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX)\n"
+ + "# Number of points: {}, mean track length: {}\n".format(
+ len(points3D), mean_track_length
+ )
+ )
+
+ with open(path, "w") as fid:
+ fid.write(HEADER)
+ for _, pt in points3D.items():
+ point_header = [pt.id, *pt.xyz, *pt.rgb, pt.error]
+ fid.write(" ".join(map(str, point_header)) + " ")
+ track_strings = []
+ for image_id, point2D in zip(pt.image_ids, pt.point2D_idxs):
+ track_strings.append(" ".join(map(str, [image_id, point2D])))
+ fid.write(" ".join(track_strings) + "\n")
+
+
+def write_points3D_binary(points3D, path_to_model_file):
+ """
+ see: src/colmap/scene/reconstruction.cc
+ void Reconstruction::ReadPoints3DBinary(const std::string& path)
+ void Reconstruction::WritePoints3DBinary(const std::string& path)
+ """
+ with open(path_to_model_file, "wb") as fid:
+ write_next_bytes(fid, len(points3D), "Q")
+ for _, pt in points3D.items():
+ write_next_bytes(fid, pt.id, "Q")
+ write_next_bytes(fid, pt.xyz.tolist(), "ddd")
+ write_next_bytes(fid, pt.rgb.tolist(), "BBB")
+ write_next_bytes(fid, pt.error, "d")
+ track_length = pt.image_ids.shape[0]
+ write_next_bytes(fid, track_length, "Q")
+ for image_id, point2D_id in zip(pt.image_ids, pt.point2D_idxs):
+ write_next_bytes(fid, [image_id, point2D_id], "ii")
+
+
+def detect_model_format(path, ext):
+ if (
+ os.path.isfile(os.path.join(path, "cameras" + ext))
+ and os.path.isfile(os.path.join(path, "images" + ext))
+ and os.path.isfile(os.path.join(path, "points3D" + ext))
+ ):
+ print("Detected model format: '" + ext + "'")
+ return True
+
+ return False
+
+
+def read_model(path, ext=""):
+ # try to detect the extension automatically
+ if ext == "":
+ if detect_model_format(path, ".bin"):
+ ext = ".bin"
+ elif detect_model_format(path, ".txt"):
+ ext = ".txt"
+ else:
+ print("Provide model format: '.bin' or '.txt'")
+ return
+
+ if ext == ".txt":
+ cameras = read_cameras_text(os.path.join(path, "cameras" + ext))
+ images = read_images_text(os.path.join(path, "images" + ext))
+ points3D = read_points3D_text(os.path.join(path, "points3D") + ext)
+ else:
+ cameras = read_cameras_binary(os.path.join(path, "cameras" + ext))
+ images = read_images_binary(os.path.join(path, "images" + ext))
+ points3D = read_points3D_binary(os.path.join(path, "points3D") + ext)
+ return cameras, images, points3D
+
+
+def write_model(cameras, images, points3D, path, ext=".bin"):
+ if ext == ".txt":
+ write_cameras_text(cameras, os.path.join(path, "cameras" + ext))
+ write_images_text(images, os.path.join(path, "images" + ext))
+ write_points3D_text(points3D, os.path.join(path, "points3D") + ext)
+ else:
+ write_cameras_binary(cameras, os.path.join(path, "cameras" + ext))
+ write_images_binary(images, os.path.join(path, "images" + ext))
+ write_points3D_binary(points3D, os.path.join(path, "points3D") + ext)
+ return cameras, images, points3D
+
+
+def qvec2rotmat(qvec):
+ return np.array(
+ [
+ [
+ 1 - 2 * qvec[2] ** 2 - 2 * qvec[3] ** 2,
+ 2 * qvec[1] * qvec[2] - 2 * qvec[0] * qvec[3],
+ 2 * qvec[3] * qvec[1] + 2 * qvec[0] * qvec[2],
+ ],
+ [
+ 2 * qvec[1] * qvec[2] + 2 * qvec[0] * qvec[3],
+ 1 - 2 * qvec[1] ** 2 - 2 * qvec[3] ** 2,
+ 2 * qvec[2] * qvec[3] - 2 * qvec[0] * qvec[1],
+ ],
+ [
+ 2 * qvec[3] * qvec[1] - 2 * qvec[0] * qvec[2],
+ 2 * qvec[2] * qvec[3] + 2 * qvec[0] * qvec[1],
+ 1 - 2 * qvec[1] ** 2 - 2 * qvec[2] ** 2,
+ ],
+ ]
+ )
+
+
+def rotmat2qvec(R):
+ Rxx, Ryx, Rzx, Rxy, Ryy, Rzy, Rxz, Ryz, Rzz = R.flat
+ K = (
+ np.array(
+ [
+ [Rxx - Ryy - Rzz, 0, 0, 0],
+ [Ryx + Rxy, Ryy - Rxx - Rzz, 0, 0],
+ [Rzx + Rxz, Rzy + Ryz, Rzz - Rxx - Ryy, 0],
+ [Ryz - Rzy, Rzx - Rxz, Rxy - Ryx, Rxx + Ryy + Rzz],
+ ]
+ )
+ / 3.0
+ )
+ eigvals, eigvecs = np.linalg.eigh(K)
+ qvec = eigvecs[[3, 0, 1, 2], np.argmax(eigvals)]
+ if qvec[0] < 0:
+ qvec *= -1
+ return qvec
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Read and write COLMAP binary and text models")
+ parser.add_argument("--input_model", help="path to input model folder")
+ parser.add_argument(
+ "--input_format",
+ choices=[".bin", ".txt"],
+ help="input model format",
+ default="",
+ )
+ parser.add_argument("--output_model", help="path to output model folder")
+ parser.add_argument(
+ "--output_format",
+ choices=[".bin", ".txt"],
+ help="output model format",
+ default=".txt",
+ )
+ args = parser.parse_args()
+
+ cameras, images, points3D = read_model(path=args.input_model, ext=args.input_format)
+
+ print("num_cameras:", len(cameras))
+ print("num_images:", len(images))
+ print("num_points3D:", len(points3D))
+
+ if args.output_model is not None:
+ write_model(
+ cameras,
+ images,
+ points3D,
+ path=args.output_model,
+ ext=args.output_format,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/cameras/source/src/depth_anything_3/utils/registry.py b/cameras/source/src/depth_anything_3/utils/registry.py
new file mode 100644
index 0000000000000000000000000000000000000000..7db16d525e0417110d87aa5e621b792d8bd95596
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/registry.py
@@ -0,0 +1,36 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Any
+from addict import Dict
+
+
+class Registry(Dict[str, Any]):
+ def __init__(self):
+ super().__init__()
+ self._map = Dict({})
+
+ def register(self, name=None):
+ def decorator(cls):
+ key = name or cls.__name__
+ self._map[key] = cls
+ return cls
+
+ return decorator
+
+ def get(self, name):
+ return self._map[name]
+
+ def all(self):
+ return self._map
diff --git a/cameras/source/src/depth_anything_3/utils/sh_helpers.py b/cameras/source/src/depth_anything_3/utils/sh_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a1a4ca8204eb8d858351afb253e41e77dfa2f74
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/sh_helpers.py
@@ -0,0 +1,88 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from math import isqrt
+import torch
+from einops import einsum
+
+try:
+ from e3nn.o3 import matrix_to_angles, wigner_D
+except ImportError:
+ from depth_anything_3.utils.logger import logger
+
+ logger.warn("Dependency 'e3nn' not found. Required for rotating the camera space SH coeff")
+
+
+def project_to_so3_strict(M: torch.Tensor) -> torch.Tensor:
+ if M.shape[-2:] != (3, 3):
+ raise ValueError("Input must be a batch of 3x3 matrices (i.e., shape [..., 3, 3]).")
+
+ # 1. Compute SVD
+ U, S, Vh = torch.linalg.svd(M)
+ V = Vh.mH
+
+ # 2. Handle reflection case (det = -1)
+ det_U = torch.det(U)
+ det_V = torch.det(V)
+ is_reflection = (det_U * det_V) < 0
+ correction_sign = torch.where(
+ is_reflection[..., None],
+ torch.tensor([1, 1, -1.0], device=M.device, dtype=M.dtype),
+ torch.tensor([1, 1, 1.0], device=M.device, dtype=M.dtype),
+ )
+ correction_matrix = torch.diag_embed(correction_sign)
+ U_corrected = U @ correction_matrix
+ R_so3_initial = U_corrected @ V.transpose(-2, -1)
+
+ # 3. Explicitly ensure determinant is 1 (or extremely close)
+ current_det = torch.det(R_so3_initial)
+ det_correction_factor = torch.pow(current_det, -1 / 3)[..., None, None]
+ R_so3_final = R_so3_initial * det_correction_factor
+
+ return R_so3_final
+
+
+def rotate_sh(
+ sh_coefficients: torch.Tensor, # "*#batch n"
+ rotations: torch.Tensor, # "*#batch 3 3"
+) -> torch.Tensor: # "*batch n"
+ # https://github.com/graphdeco-inria/gaussian-splatting/issues/176#issuecomment-2452412653
+ device = sh_coefficients.device
+ dtype = sh_coefficients.dtype
+
+ *_, n = sh_coefficients.shape
+
+ with torch.autocast(device_type=rotations.device.type, enabled=False):
+ rotations_float32 = rotations.to(torch.float32)
+
+ # switch axes: yzx -> xyz
+ P = torch.tensor([[0, 0, 1], [1, 0, 0], [0, 1, 0]]).unsqueeze(0).to(rotations_float32)
+ permuted_rotations = torch.linalg.inv(P) @ rotations_float32 @ P
+
+ # ensure rotation has det == 1 in float32 type
+ permuted_rotations_so3 = project_to_so3_strict(permuted_rotations)
+
+ alpha, beta, gamma = matrix_to_angles(permuted_rotations_so3)
+ result = []
+ for degree in range(isqrt(n)):
+ with torch.device(device):
+ sh_rotations = wigner_D(degree, alpha, -beta, gamma).type(dtype)
+ sh_rotated = einsum(
+ sh_rotations,
+ sh_coefficients[..., degree**2 : (degree + 1) ** 2],
+ "... i j, ... j -> ... i",
+ )
+ result.append(sh_rotated)
+
+ return torch.cat(result, dim=-1)
diff --git a/cameras/source/src/depth_anything_3/utils/visualize.py b/cameras/source/src/depth_anything_3/utils/visualize.py
new file mode 100644
index 0000000000000000000000000000000000000000..8fd32bddf00e5461f674525e73653409270c0227
--- /dev/null
+++ b/cameras/source/src/depth_anything_3/utils/visualize.py
@@ -0,0 +1,120 @@
+# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import matplotlib
+import numpy as np
+import torch
+from einops import rearrange
+
+from depth_anything_3.utils.logger import logger
+
+
+def visualize_depth(
+ depth: np.ndarray,
+ depth_min=None,
+ depth_max=None,
+ percentile=2,
+ ret_minmax=False,
+ ret_type=np.uint8,
+ cmap="Spectral",
+):
+ """
+ Visualize a depth map using a colormap.
+
+ Args:
+ depth: Input depth map array
+ depth_min: Minimum depth value for normalization. If None, uses percentile
+ depth_max: Maximum depth value for normalization. If None, uses percentile
+ percentile: Percentile for min/max computation if not provided
+ ret_minmax: Whether to return min/max depth values
+ ret_type: Return array type (uint8 or float)
+ cmap: Matplotlib colormap name to use
+
+ Returns:
+ Colored depth visualization as numpy array
+ If ret_minmax=True, also returns depth_min and depth_max
+ """
+ depth = depth.copy()
+ depth.copy()
+ valid_mask = depth > 0
+ depth[valid_mask] = 1 / depth[valid_mask]
+ if depth_min is None:
+ if valid_mask.sum() <= 10:
+ depth_min = 0
+ else:
+ depth_min = np.percentile(depth[valid_mask], percentile)
+ if depth_max is None:
+ if valid_mask.sum() <= 10:
+ depth_max = 0
+ else:
+ depth_max = np.percentile(depth[valid_mask], 100 - percentile)
+ if depth_min == depth_max:
+ depth_min = depth_min - 1e-6
+ depth_max = depth_max + 1e-6
+ cm = matplotlib.colormaps[cmap]
+ depth = ((depth - depth_min) / (depth_max - depth_min)).clip(0, 1)
+ depth = 1 - depth
+ img_colored_np = cm(depth[None], bytes=False)[:, :, :, 0:3] # value from 0 to 1
+ if ret_type == np.uint8:
+ img_colored_np = (img_colored_np[0] * 255.0).astype(np.uint8)
+ elif ret_type == np.float32 or ret_type == np.float64:
+ img_colored_np = img_colored_np[0]
+ else:
+ raise ValueError(f"Invalid return type: {ret_type}")
+ if ret_minmax:
+ return img_colored_np, depth_min, depth_max
+ else:
+ return img_colored_np
+
+
+# GS video rendering visulization function, since it operates in Tensor space...
+
+
+def vis_depth_map_tensor(
+ result: torch.Tensor, # "*batch height width"
+ color_map: str = "Spectral",
+) -> torch.Tensor: # "*batch 3 height with"
+ """
+ Color-map the depth map.
+ """
+ far = result.reshape(-1)[:16_000_000].float().quantile(0.99).log().to(result)
+ try:
+ near = result[result > 0][:16_000_000].float().quantile(0.01).log().to(result)
+ except (RuntimeError, ValueError) as e:
+ logger.error(f"No valid depth values found. Reason: {e}")
+ near = torch.zeros_like(far)
+ result = result.log()
+ result = (result - near) / (far - near)
+ return apply_color_map_to_image(result, color_map)
+
+
+def apply_color_map(
+ x: torch.Tensor, # " *batch"
+ color_map: str = "inferno",
+) -> torch.Tensor: # "*batch 3"
+ cmap = matplotlib.cm.get_cmap(color_map)
+
+ # Convert to NumPy so that Matplotlib color maps can be used.
+ mapped = cmap(x.float().detach().clip(min=0, max=1).cpu().numpy())[..., :3]
+
+ # Convert back to the original format.
+ return torch.tensor(mapped, device=x.device, dtype=torch.float32)
+
+
+def apply_color_map_to_image(
+ image: torch.Tensor, # "*batch height width"
+ color_map: str = "inferno",
+) -> torch.Tensor: # "*batch 3 height with"
+ image = apply_color_map(image, color_map)
+ return rearrange(image, "... h w c -> ... c h w")