annoviz 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.
annoviz-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: annoviz
3
+ Version: 0.1.0
4
+ Summary: Local YOLO annotation viewer and editor
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: pywebview>=5.0
8
+ Requires-Dist: pyobjc-core<12; sys_platform == "darwin" and python_version < "3.10"
9
+ Requires-Dist: pyobjc-framework-Cocoa<12; sys_platform == "darwin" and python_version < "3.10"
10
+ Requires-Dist: pyobjc-framework-Quartz<12; sys_platform == "darwin" and python_version < "3.10"
11
+ Requires-Dist: pyobjc-framework-Security<12; sys_platform == "darwin" and python_version < "3.10"
12
+ Requires-Dist: pyobjc-framework-UniformTypeIdentifiers<12; sys_platform == "darwin" and python_version < "3.10"
13
+ Requires-Dist: pyobjc-framework-WebKit<12; sys_platform == "darwin" and python_version < "3.10"
14
+
15
+ # Anno Viz
16
+
17
+ Anno Viz is a local YOLO annotation viewer and editor for image datasets. It opens a native desktop window backed by a web UI, shows the current image with editable bounding boxes, and includes a thumbnail timeline for moving through the dataset quickly.
18
+
19
+ ## Screenshot
20
+
21
+ ![Anno Viz screenshot](docs/screenshots/anno-viz.png)
22
+
23
+ ## Dataset Layout
24
+
25
+ Anno Viz expects a dataset directory with this structure:
26
+
27
+ ```text
28
+ dataset/
29
+ images/
30
+ image_001.jpg
31
+ image_002.jpg
32
+ labels/
33
+ image_001.txt
34
+ image_002.txt
35
+ classes.txt
36
+ ```
37
+
38
+ Labels use standard YOLO text format:
39
+
40
+ ```text
41
+ class_id x_center y_center width height
42
+ ```
43
+
44
+ The coordinate values are normalized from `0` to `1`.
45
+
46
+ ## Install
47
+
48
+ Create and activate a virtual environment if you want to keep dependencies isolated:
49
+
50
+ ```bash
51
+ python3 -m venv .venv
52
+ source .venv/bin/activate
53
+ ```
54
+
55
+ Install Anno Viz and its dependencies from the project root:
56
+
57
+ ```bash
58
+ python3 -m pip install .
59
+ ```
60
+
61
+ This installs the `annoviz` command. If you install outside a virtual environment and your shell cannot find `annoviz`, add your Python user scripts directory to `PATH`. On macOS with the system/Xcode Python this is often:
62
+
63
+ ```bash
64
+ export PATH="$HOME/Library/Python/3.9/bin:$PATH"
65
+ ```
66
+
67
+ For active development, install it in editable mode:
68
+
69
+ ```bash
70
+ python3 -m pip install -e .
71
+ ```
72
+
73
+ `pywebview` is required because the editor opens in a native desktop window. On macOS with Python 3.9, `requirements.txt` pins the PyObjC packages below version 12 because PyObjC 12 may try to build from source and fail on that toolchain.
74
+
75
+ ## Configure Dataset Directory
76
+
77
+ Set the default dataset directory for the current workspace:
78
+
79
+ ```bash
80
+ annoviz --set-dataset-dir /path/to/dataset
81
+ ```
82
+
83
+ This creates a local workspace config file named `anno_viz_config`. It is ignored by git, so each workspace can point at its own dataset.
84
+
85
+ You can also use the underscore alias:
86
+
87
+ ```bash
88
+ annoviz --set_dataset_dir /path/to/dataset
89
+ ```
90
+
91
+ ## Run
92
+
93
+ After setting the dataset directory:
94
+
95
+ ```bash
96
+ annoviz
97
+ ```
98
+
99
+ You can also run from source without installing through the compatibility wrappers:
100
+
101
+ ```bash
102
+ python3 app.py
103
+ python3 anno_viz.py
104
+ python3 -m annoviz
105
+ ```
106
+
107
+ To temporarily open a different dataset without changing the saved workspace config:
108
+
109
+ ```bash
110
+ annoviz --dataset-dir /path/to/other/dataset
111
+ annoviz -dataset_dir /path/to/other/dataset
112
+ ```
113
+
114
+ ## Optional Paths
115
+
116
+ Use these when your dataset does not follow the default `images/`, `labels/`, `classes.txt` layout:
117
+
118
+ ```bash
119
+ annoviz \
120
+ --images-dir /path/to/images \
121
+ --labels-dir /path/to/labels \
122
+ --classes-file /path/to/classes.txt
123
+ ```
124
+
125
+ Other useful options:
126
+
127
+ ```bash
128
+ annoviz --start-index 25
129
+ annoviz --port 8765
130
+ annoviz --save-dir /path/to/rendered/previews
131
+ ```
132
+
133
+ ## Controls
134
+
135
+ Control
136
+
137
+ Action
138
+
139
+ `n`, Right Arrow
140
+
141
+ Next image
142
+
143
+ `b`, Left Arrow
144
+
145
+ Previous image
146
+
147
+ `c`
148
+
149
+ Move back 5 images
150
+
151
+ `v`
152
+
153
+ Move forward 2 images
154
+
155
+ `x`
156
+
157
+ Move back 10 images
158
+
159
+ `a`
160
+
161
+ Toggle add-annotation mode
162
+
163
+ Drag box
164
+
165
+ Move or resize an annotation
166
+
167
+ `Tab`
168
+
169
+ Select next box
170
+
171
+ `+`, `-`
172
+
173
+ Change selected/add class
174
+
175
+ `0` to `9`
176
+
177
+ Set selected/add class id
178
+
179
+ Delete, Backspace
180
+
181
+ Remove selected box from the label
182
+
183
+ `s`
184
+
185
+ Save label edits
186
+
187
+ `d`
188
+
189
+ Mark current image for deletion
190
+
191
+ Click a red thumbnail
192
+
193
+ Undo pending delete for that image
194
+
195
+ Apply Deletes
196
+
197
+ Delete all marked images and matching label files
198
+
199
+ `q`, Escape, Close
200
+
201
+ Close the editor
202
+
203
+ ## Delete Flow
204
+
205
+ Press `d` to mark the current image for deletion. Marked thumbnails are shown in red.
206
+
207
+ Deletion is not applied immediately. You can undo a pending delete by clicking the red thumbnail. To permanently remove all marked images and their label files, click `Apply Deletes`.
208
+
209
+ When closing from `q`, Escape, or the `Close` button with pending deletes, Anno Viz asks whether to apply the pending deletes before closing.
210
+
211
+ ## Config
212
+
213
+ The workspace config file is:
214
+
215
+ ```text
216
+ anno_viz_config
217
+ ```
218
+
219
+ It stores JSON like:
220
+
221
+ ```json
222
+ {
223
+ "dataset_dir": "/path/to/dataset"
224
+ }
225
+ ```
226
+
227
+ This file is intentionally ignored by git because it is machine/workspace-specific.
228
+
229
+ ## Troubleshooting
230
+
231
+ If the editor says the dataset directory is not set, run:
232
+
233
+ ```bash
234
+ annoviz --set-dataset-dir /path/to/dataset
235
+ ```
236
+
237
+ If `pywebview` is missing, run:
238
+
239
+ ```bash
240
+ python3 -m pip install .
241
+ ```
242
+
243
+ If no images appear, check that your image files are inside the configured `images/` directory. Supported extensions are handled by the local image collector in `io_utils.py`.
@@ -0,0 +1,229 @@
1
+ # Anno Viz
2
+
3
+ Anno Viz is a local YOLO annotation viewer and editor for image datasets. It opens a native desktop window backed by a web UI, shows the current image with editable bounding boxes, and includes a thumbnail timeline for moving through the dataset quickly.
4
+
5
+ ## Screenshot
6
+
7
+ ![Anno Viz screenshot](docs/screenshots/anno-viz.png)
8
+
9
+ ## Dataset Layout
10
+
11
+ Anno Viz expects a dataset directory with this structure:
12
+
13
+ ```text
14
+ dataset/
15
+ images/
16
+ image_001.jpg
17
+ image_002.jpg
18
+ labels/
19
+ image_001.txt
20
+ image_002.txt
21
+ classes.txt
22
+ ```
23
+
24
+ Labels use standard YOLO text format:
25
+
26
+ ```text
27
+ class_id x_center y_center width height
28
+ ```
29
+
30
+ The coordinate values are normalized from `0` to `1`.
31
+
32
+ ## Install
33
+
34
+ Create and activate a virtual environment if you want to keep dependencies isolated:
35
+
36
+ ```bash
37
+ python3 -m venv .venv
38
+ source .venv/bin/activate
39
+ ```
40
+
41
+ Install Anno Viz and its dependencies from the project root:
42
+
43
+ ```bash
44
+ python3 -m pip install .
45
+ ```
46
+
47
+ This installs the `annoviz` command. If you install outside a virtual environment and your shell cannot find `annoviz`, add your Python user scripts directory to `PATH`. On macOS with the system/Xcode Python this is often:
48
+
49
+ ```bash
50
+ export PATH="$HOME/Library/Python/3.9/bin:$PATH"
51
+ ```
52
+
53
+ For active development, install it in editable mode:
54
+
55
+ ```bash
56
+ python3 -m pip install -e .
57
+ ```
58
+
59
+ `pywebview` is required because the editor opens in a native desktop window. On macOS with Python 3.9, `requirements.txt` pins the PyObjC packages below version 12 because PyObjC 12 may try to build from source and fail on that toolchain.
60
+
61
+ ## Configure Dataset Directory
62
+
63
+ Set the default dataset directory for the current workspace:
64
+
65
+ ```bash
66
+ annoviz --set-dataset-dir /path/to/dataset
67
+ ```
68
+
69
+ This creates a local workspace config file named `anno_viz_config`. It is ignored by git, so each workspace can point at its own dataset.
70
+
71
+ You can also use the underscore alias:
72
+
73
+ ```bash
74
+ annoviz --set_dataset_dir /path/to/dataset
75
+ ```
76
+
77
+ ## Run
78
+
79
+ After setting the dataset directory:
80
+
81
+ ```bash
82
+ annoviz
83
+ ```
84
+
85
+ You can also run from source without installing through the compatibility wrappers:
86
+
87
+ ```bash
88
+ python3 app.py
89
+ python3 anno_viz.py
90
+ python3 -m annoviz
91
+ ```
92
+
93
+ To temporarily open a different dataset without changing the saved workspace config:
94
+
95
+ ```bash
96
+ annoviz --dataset-dir /path/to/other/dataset
97
+ annoviz -dataset_dir /path/to/other/dataset
98
+ ```
99
+
100
+ ## Optional Paths
101
+
102
+ Use these when your dataset does not follow the default `images/`, `labels/`, `classes.txt` layout:
103
+
104
+ ```bash
105
+ annoviz \
106
+ --images-dir /path/to/images \
107
+ --labels-dir /path/to/labels \
108
+ --classes-file /path/to/classes.txt
109
+ ```
110
+
111
+ Other useful options:
112
+
113
+ ```bash
114
+ annoviz --start-index 25
115
+ annoviz --port 8765
116
+ annoviz --save-dir /path/to/rendered/previews
117
+ ```
118
+
119
+ ## Controls
120
+
121
+ Control
122
+
123
+ Action
124
+
125
+ `n`, Right Arrow
126
+
127
+ Next image
128
+
129
+ `b`, Left Arrow
130
+
131
+ Previous image
132
+
133
+ `c`
134
+
135
+ Move back 5 images
136
+
137
+ `v`
138
+
139
+ Move forward 2 images
140
+
141
+ `x`
142
+
143
+ Move back 10 images
144
+
145
+ `a`
146
+
147
+ Toggle add-annotation mode
148
+
149
+ Drag box
150
+
151
+ Move or resize an annotation
152
+
153
+ `Tab`
154
+
155
+ Select next box
156
+
157
+ `+`, `-`
158
+
159
+ Change selected/add class
160
+
161
+ `0` to `9`
162
+
163
+ Set selected/add class id
164
+
165
+ Delete, Backspace
166
+
167
+ Remove selected box from the label
168
+
169
+ `s`
170
+
171
+ Save label edits
172
+
173
+ `d`
174
+
175
+ Mark current image for deletion
176
+
177
+ Click a red thumbnail
178
+
179
+ Undo pending delete for that image
180
+
181
+ Apply Deletes
182
+
183
+ Delete all marked images and matching label files
184
+
185
+ `q`, Escape, Close
186
+
187
+ Close the editor
188
+
189
+ ## Delete Flow
190
+
191
+ Press `d` to mark the current image for deletion. Marked thumbnails are shown in red.
192
+
193
+ Deletion is not applied immediately. You can undo a pending delete by clicking the red thumbnail. To permanently remove all marked images and their label files, click `Apply Deletes`.
194
+
195
+ When closing from `q`, Escape, or the `Close` button with pending deletes, Anno Viz asks whether to apply the pending deletes before closing.
196
+
197
+ ## Config
198
+
199
+ The workspace config file is:
200
+
201
+ ```text
202
+ anno_viz_config
203
+ ```
204
+
205
+ It stores JSON like:
206
+
207
+ ```json
208
+ {
209
+ "dataset_dir": "/path/to/dataset"
210
+ }
211
+ ```
212
+
213
+ This file is intentionally ignored by git because it is machine/workspace-specific.
214
+
215
+ ## Troubleshooting
216
+
217
+ If the editor says the dataset directory is not set, run:
218
+
219
+ ```bash
220
+ annoviz --set-dataset-dir /path/to/dataset
221
+ ```
222
+
223
+ If `pywebview` is missing, run:
224
+
225
+ ```bash
226
+ python3 -m pip install .
227
+ ```
228
+
229
+ If no images appear, check that your image files are inside the configured `images/` directory. Supported extensions are handled by the local image collector in `io_utils.py`.
@@ -0,0 +1,3 @@
1
+ """Anno Viz package."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ from .app import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,167 @@
1
+ from pathlib import Path
2
+ import argparse
3
+ import json
4
+
5
+
6
+ CONFIG_FILE = "anno_viz_config"
7
+ LEGACY_DATASET_PATH_FILE = "anno_viz_datasetpath.txt"
8
+
9
+ DATASET_DIR_INSTRUCTIONS = """Dataset directory is not set.
10
+
11
+ Set a default dataset directory for this workspace:
12
+ annoviz --set-dataset-dir /path/to/dataset
13
+
14
+ Or temporarily visualize a different dataset for one run:
15
+ annoviz --dataset-dir /path/to/dataset
16
+ annoviz -dataset_dir /path/to/dataset
17
+
18
+ The dataset directory should contain:
19
+ images/
20
+ labels/
21
+ classes.txt
22
+ """
23
+
24
+
25
+ def workspace_config_file():
26
+ return Path.cwd() / CONFIG_FILE
27
+
28
+
29
+ def workspace_legacy_config_file():
30
+ return Path.cwd() / LEGACY_DATASET_PATH_FILE
31
+
32
+
33
+ def is_git_workspace(workspace_dir):
34
+ return (workspace_dir / ".git").exists()
35
+
36
+
37
+ def ensure_config_gitignored(workspace_dir):
38
+ if not is_git_workspace(workspace_dir):
39
+ return False
40
+
41
+ gitignore_path = workspace_dir / ".gitignore"
42
+ entry = CONFIG_FILE
43
+
44
+ if not gitignore_path.exists():
45
+ gitignore_path.write_text(f"{entry}\n", encoding="utf-8")
46
+ return True
47
+
48
+ content = gitignore_path.read_text(encoding="utf-8")
49
+ lines = [line.strip() for line in content.splitlines()]
50
+ if entry in lines:
51
+ return False
52
+
53
+ separator = "" if content.endswith("\n") or not content else "\n"
54
+ with gitignore_path.open("a", encoding="utf-8") as gitignore:
55
+ gitignore.write(f"{separator}{entry}\n")
56
+ return True
57
+
58
+
59
+ def normalize_dataset_dir(path):
60
+ path = Path(path).expanduser()
61
+ if not path.is_absolute():
62
+ path = Path.cwd() / path
63
+ return path.resolve()
64
+
65
+
66
+ def write_workspace_config(config_file, dataset_dir):
67
+ config = {
68
+ "dataset_dir": str(dataset_dir),
69
+ }
70
+ config_file.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
71
+
72
+
73
+ def read_dataset_path_text(path_file):
74
+ raw_path = path_file.read_text(encoding="utf-8").strip()
75
+ if not raw_path:
76
+ return None
77
+
78
+ path = Path(raw_path).expanduser()
79
+ if not path.is_absolute():
80
+ path = path_file.parent / path
81
+ return path.resolve()
82
+
83
+
84
+ def read_workspace_dataset_dir(config_file):
85
+ if config_file.exists():
86
+ raw_config = config_file.read_text(encoding="utf-8").strip()
87
+ if not raw_config:
88
+ return None
89
+
90
+ try:
91
+ config = json.loads(raw_config)
92
+ except json.JSONDecodeError:
93
+ return read_dataset_path_text(config_file)
94
+
95
+ dataset_dir = config.get("dataset_dir")
96
+ if not dataset_dir:
97
+ return None
98
+
99
+ path = Path(dataset_dir).expanduser()
100
+ if not path.is_absolute():
101
+ path = config_file.parent / path
102
+ return path.resolve()
103
+
104
+ legacy_path_file = workspace_legacy_config_file()
105
+ if legacy_path_file.exists():
106
+ return read_dataset_path_text(legacy_path_file)
107
+
108
+ return None
109
+
110
+
111
+ def main():
112
+ parser = argparse.ArgumentParser(description="View/edit YOLO annotations on generated images")
113
+ parser.add_argument(
114
+ "--set-dataset-dir",
115
+ "--set_dataset_dir",
116
+ dest="set_dataset_dir",
117
+ default=None,
118
+ type=Path,
119
+ help=f"Save the default dataset root to {CONFIG_FILE} in the current workspace.",
120
+ )
121
+ parser.add_argument(
122
+ "--dataset-dir",
123
+ "--dataset_dir",
124
+ "-dataset_dir",
125
+ dest="dataset_dir",
126
+ default=None,
127
+ type=Path,
128
+ help="Dataset root for this run.",
129
+ )
130
+ parser.add_argument("--images-dir", default=None, type=Path)
131
+ parser.add_argument("--labels-dir", default=None, type=Path)
132
+ parser.add_argument("--classes-file", default=None, type=Path)
133
+ parser.add_argument("--save-dir", default=None, type=Path)
134
+ parser.add_argument("--start-index", default=0, type=int)
135
+ parser.add_argument("--port", default=0, type=int, help="Local web UI port. Defaults to a free port.")
136
+
137
+ args = parser.parse_args()
138
+ config_file = workspace_config_file()
139
+ if args.set_dataset_dir is not None:
140
+ dataset_dir = normalize_dataset_dir(args.set_dataset_dir)
141
+ write_workspace_config(config_file, dataset_dir)
142
+ print(f"saved dataset directory to {config_file}: {dataset_dir}")
143
+ legacy_path_file = workspace_legacy_config_file()
144
+ if legacy_path_file.exists():
145
+ legacy_path_file.unlink()
146
+ ensure_config_gitignored(config_file.parent)
147
+ return
148
+
149
+ dataset_dir = args.dataset_dir.expanduser() if args.dataset_dir is not None else read_workspace_dataset_dir(config_file)
150
+ if dataset_dir is None:
151
+ parser.exit(2, f"error: {DATASET_DIR_INSTRUCTIONS}\n")
152
+
153
+ dataset_dir = normalize_dataset_dir(dataset_dir)
154
+ from .web_editor import visualize
155
+
156
+ visualize(
157
+ images_dir=args.images_dir or dataset_dir / "images",
158
+ labels_dir=args.labels_dir or dataset_dir / "labels",
159
+ classes_file=args.classes_file or dataset_dir / "classes.txt",
160
+ save_dir=args.save_dir,
161
+ start_index=args.start_index,
162
+ port=args.port,
163
+ )
164
+
165
+
166
+ if __name__ == "__main__":
167
+ main()