vars-localize 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vars_localize-0.1.0/.gitignore +132 -0
- vars_localize-0.1.0/PKG-INFO +48 -0
- vars_localize-0.1.0/README.md +37 -0
- vars_localize-0.1.0/pyproject.toml +31 -0
- vars_localize-0.1.0/src/vars_localize/__init__.py +0 -0
- vars_localize-0.1.0/src/vars_localize/__main__.py +34 -0
- vars_localize-0.1.0/src/vars_localize/assets/__init__.py +28 -0
- vars_localize-0.1.0/src/vars_localize/assets/images/arrow_left.png +0 -0
- vars_localize-0.1.0/src/vars_localize/assets/images/arrow_right.png +0 -0
- vars_localize-0.1.0/src/vars_localize/ui/AppWindow.py +276 -0
- vars_localize-0.1.0/src/vars_localize/ui/BoundingBox.py +243 -0
- vars_localize-0.1.0/src/vars_localize/ui/ConceptEntry.py +33 -0
- vars_localize-0.1.0/src/vars_localize/ui/ConceptSearchbar.py +29 -0
- vars_localize-0.1.0/src/vars_localize/ui/DisplayPanel.py +28 -0
- vars_localize-0.1.0/src/vars_localize/ui/EntryTree.py +504 -0
- vars_localize-0.1.0/src/vars_localize/ui/ImageView.py +743 -0
- vars_localize-0.1.0/src/vars_localize/ui/JSONTree.py +32 -0
- vars_localize-0.1.0/src/vars_localize/ui/LoginDialog.py +67 -0
- vars_localize-0.1.0/src/vars_localize/ui/Paginator.py +95 -0
- vars_localize-0.1.0/src/vars_localize/ui/PropertiesDialog.py +43 -0
- vars_localize-0.1.0/src/vars_localize/ui/PropertiesForm.py +66 -0
- vars_localize-0.1.0/src/vars_localize/ui/SearchPanel.py +232 -0
- vars_localize-0.1.0/src/vars_localize/ui/__init__.py +0 -0
- vars_localize-0.1.0/src/vars_localize/util/__init__.py +0 -0
- vars_localize-0.1.0/src/vars_localize/util/endpoints.py +115 -0
- vars_localize-0.1.0/src/vars_localize/util/m3.py +481 -0
- vars_localize-0.1.0/src/vars_localize/util/utils.py +90 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
# build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.nox/
|
|
44
|
+
.coverage
|
|
45
|
+
.coverage.*
|
|
46
|
+
.cache
|
|
47
|
+
nosetests.xml
|
|
48
|
+
coverage.xml
|
|
49
|
+
*.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
|
|
53
|
+
# Translations
|
|
54
|
+
*.mo
|
|
55
|
+
*.pot
|
|
56
|
+
|
|
57
|
+
# Django stuff:
|
|
58
|
+
*.log
|
|
59
|
+
local_settings.py
|
|
60
|
+
db.sqlite3
|
|
61
|
+
db.sqlite3-journal
|
|
62
|
+
|
|
63
|
+
# Flask stuff:
|
|
64
|
+
instance/
|
|
65
|
+
.webassets-cache
|
|
66
|
+
|
|
67
|
+
# Scrapy stuff:
|
|
68
|
+
.scrapy
|
|
69
|
+
|
|
70
|
+
# Sphinx documentation
|
|
71
|
+
docs/_build/
|
|
72
|
+
|
|
73
|
+
# PyBuilder
|
|
74
|
+
target/
|
|
75
|
+
|
|
76
|
+
# Jupyter Notebook
|
|
77
|
+
.ipynb_checkpoints
|
|
78
|
+
|
|
79
|
+
# IPython
|
|
80
|
+
profile_default/
|
|
81
|
+
ipython_config.py
|
|
82
|
+
|
|
83
|
+
# pyenv
|
|
84
|
+
.python-version
|
|
85
|
+
|
|
86
|
+
# pipenv
|
|
87
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
88
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
89
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
90
|
+
# install all needed dependencies.
|
|
91
|
+
#Pipfile.lock
|
|
92
|
+
|
|
93
|
+
# celery beat schedule file
|
|
94
|
+
celerybeat-schedule
|
|
95
|
+
|
|
96
|
+
# SageMath parsed files
|
|
97
|
+
*.sage.py
|
|
98
|
+
|
|
99
|
+
# Environments
|
|
100
|
+
.env
|
|
101
|
+
.venv
|
|
102
|
+
env/
|
|
103
|
+
venv/
|
|
104
|
+
ENV/
|
|
105
|
+
env.bak/
|
|
106
|
+
venv.bak/
|
|
107
|
+
|
|
108
|
+
# Spyder project settings
|
|
109
|
+
.spyderproject
|
|
110
|
+
.spyproject
|
|
111
|
+
|
|
112
|
+
# Rope project settings
|
|
113
|
+
.ropeproject
|
|
114
|
+
|
|
115
|
+
# mkdocs documentation
|
|
116
|
+
/site
|
|
117
|
+
|
|
118
|
+
# mypy
|
|
119
|
+
.mypy_cache/
|
|
120
|
+
.dmypy.json
|
|
121
|
+
dmypy.json
|
|
122
|
+
|
|
123
|
+
# Pyre type checker
|
|
124
|
+
.pyre/
|
|
125
|
+
|
|
126
|
+
# PyCharm
|
|
127
|
+
.idea/
|
|
128
|
+
|
|
129
|
+
# Project specific
|
|
130
|
+
cache/
|
|
131
|
+
requirements.lock
|
|
132
|
+
requirements-dev.lock
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: vars-localize
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tool for creating localization with VARS.
|
|
5
|
+
Author-email: Kevin Barnard <kbarnard@mbari.org>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Requires-Dist: pyqt6>=6.7.1
|
|
8
|
+
Requires-Dist: qdarkstyle>=3.2.3
|
|
9
|
+
Requires-Dist: requests>=2.32.3
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# vars-localize
|
|
13
|
+
Tool for creating localizations within the VARS database.
|
|
14
|
+
|
|
15
|
+
Author: Kevin Barnard ([kbarnard@mbari.org](mailto:kbarnard@mbari.org))
|
|
16
|
+
|
|
17
|
+
## :hammer: Installation
|
|
18
|
+
|
|
19
|
+
> [!NOTE]
|
|
20
|
+
> VARS Localize requires Python 3.8 or later.
|
|
21
|
+
|
|
22
|
+
To install VARS Localize, run:
|
|
23
|
+
```bash
|
|
24
|
+
pip install vars-localize
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## :rocket: Usage
|
|
28
|
+
|
|
29
|
+
To start the application, run:
|
|
30
|
+
```bash
|
|
31
|
+
vars-localize [-u URL]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Once the application launches, log in with your VARS username and password.
|
|
35
|
+
|
|
36
|
+
Search for a concept in the bar at the top left of the application, then select a concept from the list of results to populate a tree of imaged moments in the pane below.
|
|
37
|
+
Select an observation from the children in the subtree of the imaged moment, and draw a bounding box around the observed concept by clicking and dragging.
|
|
38
|
+
|
|
39
|
+
You can double-click on any localization to edit its properties in a dialog.
|
|
40
|
+
Additionally, a localization can be resized by dragging the square corners of its bounding box.
|
|
41
|
+
|
|
42
|
+
## Credits
|
|
43
|
+
|
|
44
|
+
VARS Localize is made with [PyQt6](https://pypi.org/project/PyQt6/).
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
Copyright © 2019 [Monterey Bay Aquarium Research Institute](https://www.mbari.org/)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# vars-localize
|
|
2
|
+
Tool for creating localizations within the VARS database.
|
|
3
|
+
|
|
4
|
+
Author: Kevin Barnard ([kbarnard@mbari.org](mailto:kbarnard@mbari.org))
|
|
5
|
+
|
|
6
|
+
## :hammer: Installation
|
|
7
|
+
|
|
8
|
+
> [!NOTE]
|
|
9
|
+
> VARS Localize requires Python 3.8 or later.
|
|
10
|
+
|
|
11
|
+
To install VARS Localize, run:
|
|
12
|
+
```bash
|
|
13
|
+
pip install vars-localize
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## :rocket: Usage
|
|
17
|
+
|
|
18
|
+
To start the application, run:
|
|
19
|
+
```bash
|
|
20
|
+
vars-localize [-u URL]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Once the application launches, log in with your VARS username and password.
|
|
24
|
+
|
|
25
|
+
Search for a concept in the bar at the top left of the application, then select a concept from the list of results to populate a tree of imaged moments in the pane below.
|
|
26
|
+
Select an observation from the children in the subtree of the imaged moment, and draw a bounding box around the observed concept by clicking and dragging.
|
|
27
|
+
|
|
28
|
+
You can double-click on any localization to edit its properties in a dialog.
|
|
29
|
+
Additionally, a localization can be resized by dragging the square corners of its bounding box.
|
|
30
|
+
|
|
31
|
+
## Credits
|
|
32
|
+
|
|
33
|
+
VARS Localize is made with [PyQt6](https://pypi.org/project/PyQt6/).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
Copyright © 2019 [Monterey Bay Aquarium Research Institute](https://www.mbari.org/)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "vars-localize"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Tool for creating localization with VARS."
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Kevin Barnard", email = "kbarnard@mbari.org" }
|
|
7
|
+
]
|
|
8
|
+
dependencies = [
|
|
9
|
+
"pyqt6>=6.7.1",
|
|
10
|
+
"requests>=2.32.3",
|
|
11
|
+
"qdarkstyle>=3.2.3",
|
|
12
|
+
]
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">= 3.8"
|
|
15
|
+
|
|
16
|
+
[project.scripts]
|
|
17
|
+
vars-localize = "vars_localize.__main__:main"
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["hatchling"]
|
|
21
|
+
build-backend = "hatchling.build"
|
|
22
|
+
|
|
23
|
+
[tool.rye]
|
|
24
|
+
managed = true
|
|
25
|
+
dev-dependencies = []
|
|
26
|
+
|
|
27
|
+
[tool.hatch.metadata]
|
|
28
|
+
allow-direct-references = true
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.targets.wheel]
|
|
31
|
+
packages = ["src/vars_localize"]
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main entry point for the VARS Localize application.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from PyQt6.QtWidgets import QApplication
|
|
9
|
+
|
|
10
|
+
from vars_localize.ui.AppWindow import AppWindow
|
|
11
|
+
from vars_localize.util.endpoints import DEFAULT_M3_URL
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
"""
|
|
16
|
+
Main entry point for the VARS Localize application.
|
|
17
|
+
"""
|
|
18
|
+
parser = argparse.ArgumentParser(description="VARS Localize")
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"-u", "--url", type=str, default=DEFAULT_M3_URL, help="URL of M3 server"
|
|
21
|
+
)
|
|
22
|
+
args = parser.parse_args()
|
|
23
|
+
|
|
24
|
+
app = QApplication(sys.argv)
|
|
25
|
+
|
|
26
|
+
window = AppWindow(args.url)
|
|
27
|
+
window.show()
|
|
28
|
+
|
|
29
|
+
exit_code = app.exec()
|
|
30
|
+
sys.exit(exit_code)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
main()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helpers for dealing with assets.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
ASSETS_DIR = Path(__file__).parent.resolve()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_asset_path(rel_path: Union[str, Path]) -> Path:
|
|
13
|
+
"""
|
|
14
|
+
Get the absolute path to an asset.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
rel_path (Union[str, Path]): The relative path to the asset.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Path: The absolute path to the asset.
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
ValueError: If rel_path is an absolute path.
|
|
24
|
+
"""
|
|
25
|
+
rel_path = Path(rel_path)
|
|
26
|
+
if rel_path.is_absolute():
|
|
27
|
+
raise ValueError("rel_path must be a relative path.")
|
|
28
|
+
return ASSETS_DIR / rel_path
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main application window.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from PyQt6.QtCore import Qt
|
|
8
|
+
from PyQt6.QtGui import QCloseEvent, QIcon, QAction
|
|
9
|
+
from PyQt6.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QMessageBox, QInputDialog
|
|
10
|
+
|
|
11
|
+
from vars_localize.ui.EntryTree import EntryTreeItem
|
|
12
|
+
from vars_localize.ui.LoginDialog import LoginDialog
|
|
13
|
+
from vars_localize.ui.DisplayPanel import DisplayPanel
|
|
14
|
+
from vars_localize.ui.SearchPanel import SearchPanel
|
|
15
|
+
from vars_localize.util.m3 import (
|
|
16
|
+
check_connection,
|
|
17
|
+
get_all_users,
|
|
18
|
+
get_annotations_by_video_refernce,
|
|
19
|
+
get_imaged_moments_by_image_reference,
|
|
20
|
+
)
|
|
21
|
+
from vars_localize.util.utils import log, split_comma_list
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AppWindow(QMainWindow):
|
|
25
|
+
def __init__(self, m3_url: str, parent=None):
|
|
26
|
+
super(AppWindow, self).__init__(parent)
|
|
27
|
+
self._m3_url = m3_url.rstrip("/")
|
|
28
|
+
|
|
29
|
+
self.setWindowTitle("VARS Localize")
|
|
30
|
+
|
|
31
|
+
log(f"Checking connection to M3 at {self._m3_url}...")
|
|
32
|
+
if not check_connection(self._m3_url):
|
|
33
|
+
log(
|
|
34
|
+
"You are not connected to M3. Check your internet connection and/or VPN.",
|
|
35
|
+
level=2,
|
|
36
|
+
)
|
|
37
|
+
QMessageBox.critical(
|
|
38
|
+
self,
|
|
39
|
+
"No connection to M3",
|
|
40
|
+
"You are not connected to M3. Check your internet connection and/or VPN.",
|
|
41
|
+
)
|
|
42
|
+
exit(1)
|
|
43
|
+
log("Connected.")
|
|
44
|
+
|
|
45
|
+
self.observer = None
|
|
46
|
+
self.observer_role = None
|
|
47
|
+
self.admin_mode = False
|
|
48
|
+
|
|
49
|
+
login_ok = self.login()
|
|
50
|
+
if not login_ok:
|
|
51
|
+
log("You must log in to use this tool.", level=2)
|
|
52
|
+
exit(1)
|
|
53
|
+
|
|
54
|
+
self.central_container = QWidget()
|
|
55
|
+
self.central_container.setLayout(QHBoxLayout())
|
|
56
|
+
|
|
57
|
+
self.search_panel = SearchPanel(parent=self)
|
|
58
|
+
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.search_panel)
|
|
59
|
+
|
|
60
|
+
self.display_panel = DisplayPanel(parent=self)
|
|
61
|
+
self.central_container.layout().addWidget(self.display_panel)
|
|
62
|
+
|
|
63
|
+
self.setCentralWidget(self.central_container)
|
|
64
|
+
|
|
65
|
+
# Add admin menu if available to user
|
|
66
|
+
if self.observer_role in ("Maint", "Admin"):
|
|
67
|
+
self.add_admin_menu()
|
|
68
|
+
|
|
69
|
+
self.add_search_menu()
|
|
70
|
+
self.add_video_menu()
|
|
71
|
+
|
|
72
|
+
self.display_panel.image_view.observer = self.observer
|
|
73
|
+
self.display_panel.image_view.select_next = self.search_panel.select_next
|
|
74
|
+
self.display_panel.image_view.select_prev = self.search_panel.select_prev
|
|
75
|
+
|
|
76
|
+
self.search_panel.observer = self.observer
|
|
77
|
+
|
|
78
|
+
def load_entry(self, current: EntryTreeItem, previous: EntryTreeItem):
|
|
79
|
+
"""
|
|
80
|
+
Load the current entry into the display panel
|
|
81
|
+
:param current: Current selected entry
|
|
82
|
+
:param previous: Previously selected entry
|
|
83
|
+
:return: None
|
|
84
|
+
"""
|
|
85
|
+
if current and current.metadata:
|
|
86
|
+
self.display_panel.load_entry(current)
|
|
87
|
+
|
|
88
|
+
def login(self):
|
|
89
|
+
"""
|
|
90
|
+
Prompt for observer login
|
|
91
|
+
:return: None
|
|
92
|
+
"""
|
|
93
|
+
login_dialog = LoginDialog(parent=self)
|
|
94
|
+
login_dialog._login_form._username_line_edit.setFocus()
|
|
95
|
+
ok = login_dialog.exec()
|
|
96
|
+
|
|
97
|
+
if ok:
|
|
98
|
+
# Get the username/password from the dialog
|
|
99
|
+
username, password = login_dialog.credentials
|
|
100
|
+
|
|
101
|
+
# Set up the M3 configuration, returning False if login fails
|
|
102
|
+
if not self.configure_m3(username, password):
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
all_valid_users = get_all_users()
|
|
106
|
+
users_dict = {
|
|
107
|
+
user_data["username"]: user_data for user_data in all_valid_users
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Set the observer and role
|
|
111
|
+
self.observer = username
|
|
112
|
+
self.observer_role = users_dict[username]["role"]
|
|
113
|
+
else: # Login cancel, return failure
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
return True # Return success
|
|
117
|
+
|
|
118
|
+
def configure_m3(self, username, password) -> bool:
|
|
119
|
+
"""
|
|
120
|
+
Configure endpoints and set up Annosaurus auth
|
|
121
|
+
"""
|
|
122
|
+
from vars_localize.util.endpoints import configure as configure_endpoints
|
|
123
|
+
from vars_localize.util.m3 import configure_anno_session
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
configure_endpoints(self._m3_url, username, password)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
log("Login failed.", level=2)
|
|
129
|
+
log(e, level=2)
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
configure_anno_session()
|
|
133
|
+
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
def add_admin_menu(self):
|
|
137
|
+
"""
|
|
138
|
+
Add the admin menu for observation modification/deletion
|
|
139
|
+
"""
|
|
140
|
+
main_menu = self.menuBar()
|
|
141
|
+
options_menu = main_menu.addMenu("&Options")
|
|
142
|
+
|
|
143
|
+
admin_mode_action = QAction("Admin Mode", options_menu, checkable=True)
|
|
144
|
+
|
|
145
|
+
def set_admin_mode(val):
|
|
146
|
+
if val:
|
|
147
|
+
QMessageBox.warning(
|
|
148
|
+
self,
|
|
149
|
+
"Entering Admin Mode",
|
|
150
|
+
"WARNING: You are now entering administrator mode. This mode allows modification and deletion of observations within VARS.",
|
|
151
|
+
)
|
|
152
|
+
self.admin_mode = val
|
|
153
|
+
|
|
154
|
+
admin_mode_action.toggled.connect(set_admin_mode)
|
|
155
|
+
options_menu.addAction(admin_mode_action)
|
|
156
|
+
|
|
157
|
+
def add_search_menu(self):
|
|
158
|
+
"""
|
|
159
|
+
Add the Go menu for non-concept searches
|
|
160
|
+
"""
|
|
161
|
+
main_menu = self.menuBar()
|
|
162
|
+
search_menu = main_menu.addMenu("&Search")
|
|
163
|
+
|
|
164
|
+
def search_imaged_moment():
|
|
165
|
+
imaged_moment_uuid_list, ok = QInputDialog.getText(
|
|
166
|
+
self,
|
|
167
|
+
"Imaged Moment UUID Search",
|
|
168
|
+
"Imaged Moment UUID (or comma-separated list)",
|
|
169
|
+
)
|
|
170
|
+
if ok:
|
|
171
|
+
imaged_moment_uuids = split_comma_list(imaged_moment_uuid_list)
|
|
172
|
+
imaged_moment_uuids = list(
|
|
173
|
+
set(imaged_moment_uuids)
|
|
174
|
+
) # Ensure no duplicates
|
|
175
|
+
|
|
176
|
+
# Set the UUIDs and load the first page
|
|
177
|
+
self.search_panel.set_uuids(imaged_moment_uuids)
|
|
178
|
+
self.search_panel.load_page()
|
|
179
|
+
|
|
180
|
+
search_imaged_moment_action = QAction("Imaged Moment UUID", search_menu)
|
|
181
|
+
search_imaged_moment_action.triggered.connect(search_imaged_moment)
|
|
182
|
+
search_menu.addAction(search_imaged_moment_action)
|
|
183
|
+
|
|
184
|
+
def search_image_reference():
|
|
185
|
+
image_reference_uuid_list, ok = QInputDialog.getText(
|
|
186
|
+
self,
|
|
187
|
+
"Image Reference UUID Search",
|
|
188
|
+
"Image Reference UUID (or comma-separated list)",
|
|
189
|
+
)
|
|
190
|
+
if ok:
|
|
191
|
+
all_image_reference_uuids = split_comma_list(image_reference_uuid_list)
|
|
192
|
+
imaged_moment_uuids = []
|
|
193
|
+
for image_reference_uuid in all_image_reference_uuids:
|
|
194
|
+
res = get_imaged_moments_by_image_reference(image_reference_uuid)
|
|
195
|
+
if res:
|
|
196
|
+
imaged_moment_uuids.extend(
|
|
197
|
+
[item["imaged_moment_uuid"] for item in res]
|
|
198
|
+
)
|
|
199
|
+
imaged_moment_uuids = list(
|
|
200
|
+
set(imaged_moment_uuids)
|
|
201
|
+
) # Ensure no duplicates
|
|
202
|
+
|
|
203
|
+
# Set the UUIDs and load the first page
|
|
204
|
+
self.search_panel.set_uuids(imaged_moment_uuids)
|
|
205
|
+
self.search_panel.load_page()
|
|
206
|
+
|
|
207
|
+
search_image_reference_action = QAction("Image Reference UUID", search_menu)
|
|
208
|
+
search_image_reference_action.triggered.connect(search_image_reference)
|
|
209
|
+
search_menu.addAction(search_image_reference_action)
|
|
210
|
+
|
|
211
|
+
def search_video_reference():
|
|
212
|
+
video_reference_uuid, ok = QInputDialog.getText(
|
|
213
|
+
self, "Video Reference UUID Search", "Video Reference UUID"
|
|
214
|
+
)
|
|
215
|
+
if ok:
|
|
216
|
+
res = get_annotations_by_video_refernce(video_reference_uuid)
|
|
217
|
+
if res:
|
|
218
|
+
timestamp_uuid_tuples = set()
|
|
219
|
+
for item in res:
|
|
220
|
+
timestamp = datetime.now()
|
|
221
|
+
if "recorded_timestamp" in item:
|
|
222
|
+
try:
|
|
223
|
+
timestamp = datetime.strptime(
|
|
224
|
+
item["recorded_timestamp"], "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
225
|
+
)
|
|
226
|
+
except ValueError:
|
|
227
|
+
timestamp = datetime.strptime(
|
|
228
|
+
item["recorded_timestamp"], "%Y-%m-%dT%H:%M:%SZ"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
timestamp_uuid_tuples.add(
|
|
232
|
+
(timestamp, item["imaged_moment_uuid"])
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Sort by timestamp, then UUID
|
|
236
|
+
timestamp_uuid_tuples = sorted(timestamp_uuid_tuples)
|
|
237
|
+
imaged_moment_uuids = [item[1] for item in timestamp_uuid_tuples]
|
|
238
|
+
|
|
239
|
+
# Set the UUIDs and load the first page
|
|
240
|
+
self.search_panel.set_uuids(imaged_moment_uuids)
|
|
241
|
+
self.search_panel.load_page()
|
|
242
|
+
else:
|
|
243
|
+
# No results, warning dialog
|
|
244
|
+
QMessageBox.warning(
|
|
245
|
+
self,
|
|
246
|
+
"No Results",
|
|
247
|
+
"No results found for video reference UUID: {}".format(
|
|
248
|
+
video_reference_uuid
|
|
249
|
+
),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
search_video_reference_action = QAction("Video Reference UUID", search_menu)
|
|
253
|
+
search_video_reference_action.triggered.connect(search_video_reference)
|
|
254
|
+
search_menu.addAction(search_video_reference_action)
|
|
255
|
+
|
|
256
|
+
def add_video_menu(self):
|
|
257
|
+
"""
|
|
258
|
+
Add the Video menu for video-level operations
|
|
259
|
+
"""
|
|
260
|
+
main_menu = self.menuBar()
|
|
261
|
+
video_menu = main_menu.addMenu("&Video")
|
|
262
|
+
|
|
263
|
+
def open_video():
|
|
264
|
+
self.search_panel.open_video()
|
|
265
|
+
|
|
266
|
+
open_video_action = QAction("Open Video", video_menu)
|
|
267
|
+
open_video_action.triggered.connect(open_video)
|
|
268
|
+
video_menu.addAction(open_video_action)
|
|
269
|
+
|
|
270
|
+
def closeEvent(self, a0: QCloseEvent) -> None:
|
|
271
|
+
"""
|
|
272
|
+
Detect window close and tear down components
|
|
273
|
+
:param a0: Close event
|
|
274
|
+
:return: None
|
|
275
|
+
"""
|
|
276
|
+
self.deleteLater()
|