lannotex 0.1.0__tar.gz → 0.1.1__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.
- {lannotex-0.1.0 → lannotex-0.1.1}/CHANGELOG.md +9 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/PKG-INFO +4 -4
- {lannotex-0.1.0 → lannotex-0.1.1}/README.md +3 -3
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/guide/import-export.md +28 -8
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/__init__.py +1 -1
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/exporters.py +28 -3
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex.egg-info/PKG-INFO +4 -4
- {lannotex-0.1.0 → lannotex-0.1.1}/tests/test_exporters.py +27 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/LICENSE +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/LICENSES/GPL-3.0-only.txt +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/LICENSES/LGPL-3.0-only.txt +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/LICENSES/Qt-GPL-exception-1.0.txt +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/LICENSES/THIRD_PARTY_NOTICES.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/Lannotex.spec +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/MANIFEST.in +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/MICROSOFT_STORE.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/PERFORMANCE.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/ROADMAP.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/TEAM.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/VERIFICATION.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/cli.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/getting-started/installation.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/getting-started/quickstart.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/guide/annotating.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/guide/projects.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/index.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/docs/migrate-from-labelme.md +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/pyproject.toml +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/requirements-windows-build.txt +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/run_lannotex.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/scripts/benchmark.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/scripts/build_linux.sh +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/scripts/build_macos.sh +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/scripts/build_windows.ps1 +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/scripts/collect_notices.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/scripts/verify_executable.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/setup.cfg +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/__main__.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/app.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/canvas.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/cli.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/domain.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/home.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/models.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/py.typed +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/storage.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/team.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/theme.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex/workers.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex.egg-info/SOURCES.txt +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex.egg-info/dependency_links.txt +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex.egg-info/entry_points.txt +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex.egg-info/requires.txt +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/src/lannotex.egg-info/top_level.txt +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/tests/test_cli.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/tests/test_domain.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/tests/test_storage.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/tests/test_team.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/tests/test_ui.py +0 -0
- {lannotex-0.1.0 → lannotex-0.1.1}/tests/test_workflows.py +0 -0
|
@@ -6,6 +6,15 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.1] — 2026-10-14
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **YOLO export is ready to train with Ultralytics:** `images/` (hard-linked when
|
|
14
|
+
possible, otherwise copied), matching `labels/`, and a `data.yaml` with class names.
|
|
15
|
+
Images that haven't been annotated yet are left out, so they don't become false
|
|
16
|
+
negatives.
|
|
17
|
+
|
|
9
18
|
## [0.1.0] — 2026-10-14
|
|
10
19
|
|
|
11
20
|
First public release on PyPI: `pip install lannotex` or `uv tool install lannotex`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lannotex
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Fast, offline-first image annotation for classifications, boxes, and polygons.
|
|
5
5
|
Author: Lourdhu Raju
|
|
6
6
|
Maintainer: Lourdhu Raju
|
|
@@ -133,7 +133,7 @@ Every shortcut can be remapped. See the [annotating guide](https://lourdhu02.git
|
|
|
133
133
|
| **Annotate** | Bounding boxes · polygons with vertex drag, insert, and delete · multi-label image classes · "no target" negatives · 100-step undo/redo · zoom under the pointer, pan, arrow-key nudging · focus mode · light and dark themes |
|
|
134
134
|
| **Stay safe** | SQLite projects with versioned migrations · recovery drafts and 700 ms autosave · transactional saves · geometry validation · backup and restore · relinking for moved images · EXIF orientation handled |
|
|
135
135
|
| **Scale** | Virtualized image list (at most 1,024 records in memory) · background import and decoding · 96 MB preview cache · coordinates normalized to the original resolution |
|
|
136
|
-
| **Export** | Labelme JSON · YOLO · COCO JSON · classification CSV · lossless native JSONL · each export reports anything it skipped |
|
|
136
|
+
| **Export** | Labelme JSON · ready-to-train YOLO (`images/`, `labels/`, `data.yaml`) · COCO JSON · classification CSV · lossless native JSONL · each export reports anything it skipped |
|
|
137
137
|
| **Automate** | `lannotex export` and `lannotex analyze` run headless in CI, with no display needed |
|
|
138
138
|
| **Collaborate** | Optional LAN host with per-reviewer tokens, background sync, revision conflicts you resolve explicitly, revocation, and an audit history |
|
|
139
139
|
|
|
@@ -181,8 +181,8 @@ hashes. There is no telemetry, no external AI service, and no automatic upload.
|
|
|
181
181
|
|
|
182
182
|
## Roadmap
|
|
183
183
|
|
|
184
|
-
Next up:
|
|
185
|
-
|
|
184
|
+
Next up: more LabelMe shape types (points, lines, circles), VOC and YOLO-seg export, and
|
|
185
|
+
number-key label selection. See the
|
|
186
186
|
[roadmap](https://lourdhu02.github.io/Lannotex/ROADMAP/) and the
|
|
187
187
|
[measured performance](https://lourdhu02.github.io/Lannotex/PERFORMANCE/).
|
|
188
188
|
|
|
@@ -91,7 +91,7 @@ Every shortcut can be remapped. See the [annotating guide](https://lourdhu02.git
|
|
|
91
91
|
| **Annotate** | Bounding boxes · polygons with vertex drag, insert, and delete · multi-label image classes · "no target" negatives · 100-step undo/redo · zoom under the pointer, pan, arrow-key nudging · focus mode · light and dark themes |
|
|
92
92
|
| **Stay safe** | SQLite projects with versioned migrations · recovery drafts and 700 ms autosave · transactional saves · geometry validation · backup and restore · relinking for moved images · EXIF orientation handled |
|
|
93
93
|
| **Scale** | Virtualized image list (at most 1,024 records in memory) · background import and decoding · 96 MB preview cache · coordinates normalized to the original resolution |
|
|
94
|
-
| **Export** | Labelme JSON · YOLO · COCO JSON · classification CSV · lossless native JSONL · each export reports anything it skipped |
|
|
94
|
+
| **Export** | Labelme JSON · ready-to-train YOLO (`images/`, `labels/`, `data.yaml`) · COCO JSON · classification CSV · lossless native JSONL · each export reports anything it skipped |
|
|
95
95
|
| **Automate** | `lannotex export` and `lannotex analyze` run headless in CI, with no display needed |
|
|
96
96
|
| **Collaborate** | Optional LAN host with per-reviewer tokens, background sync, revision conflicts you resolve explicitly, revocation, and an audit history |
|
|
97
97
|
|
|
@@ -139,8 +139,8 @@ hashes. There is no telemetry, no external AI service, and no automatic upload.
|
|
|
139
139
|
|
|
140
140
|
## Roadmap
|
|
141
141
|
|
|
142
|
-
Next up:
|
|
143
|
-
|
|
142
|
+
Next up: more LabelMe shape types (points, lines, circles), VOC and YOLO-seg export, and
|
|
143
|
+
number-key label selection. See the
|
|
144
144
|
[roadmap](https://lourdhu02.github.io/Lannotex/ROADMAP/) and the
|
|
145
145
|
[measured performance](https://lourdhu02.github.io/Lannotex/PERFORMANCE/).
|
|
146
146
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
| Format | Contains | Use it for |
|
|
6
6
|
| --- | --- | --- |
|
|
7
7
|
| **Labelme JSON** | One JSON per image: boxes as `rectangle`, polygons, classes as `flags` | LabelMe and tools that read its format |
|
|
8
|
-
| **YOLO boxes** |
|
|
8
|
+
| **YOLO boxes** | Ready-to-train Ultralytics dataset: `images/`, `labels/`, `data.yaml` | YOLO detection training |
|
|
9
9
|
| **COCO JSON** | One `coco.json` with boxes, polygon segmentations, and areas | Detection and instance segmentation |
|
|
10
10
|
| **Classification CSV** | One row per image, one 0/1 column per class | Image classification |
|
|
11
11
|
| **Native JSONL** | Every annotation with its ID and status | Lossless backup and round-trips |
|
|
@@ -24,10 +24,11 @@ Lannotex-export-1a2b3c4d/
|
|
|
24
24
|
├── classification.csv
|
|
25
25
|
├── images.jsonl
|
|
26
26
|
├── annotations.jsonl
|
|
27
|
-
└── 00000001_street.json # per-image files (Labelme JSON
|
|
27
|
+
└── 00000001_street.json # per-image files (Labelme JSON)
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
Images are **referenced, not copied**: exported records point to the original files.
|
|
30
|
+
Images are **referenced, not copied**: exported records point to the original files. YOLO
|
|
31
|
+
is the exception, described below.
|
|
31
32
|
|
|
32
33
|
### What gets skipped
|
|
33
34
|
|
|
@@ -39,11 +40,30 @@ An image is left out of the export, and listed in `manifest.json`, if:
|
|
|
39
40
|
|
|
40
41
|
Run **View → Analysis** or `lannotex analyze` beforehand to find these images.
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
### YOLO: ready to train
|
|
44
|
+
|
|
45
|
+
The YOLO export is a complete [Ultralytics](https://docs.ultralytics.com/datasets/detect/)
|
|
46
|
+
dataset:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
Lannotex-export-1a2b3c4d/
|
|
50
|
+
├── data.yaml # path, train/val, class names
|
|
51
|
+
├── images/00000001_street.jpg
|
|
52
|
+
└── labels/00000001_street.txt # class cx cy w h, normalized
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
yolo detect train data=Lannotex-export-1a2b3c4d/data.yaml model=yolo11n.pt
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- Images are **hard-linked** into `images/` when the export is on the same drive, so they
|
|
60
|
+
take no extra space. Otherwise they're copied. Your originals are never modified.
|
|
61
|
+
- Images marked **No target objects** get an empty label file, so YOLO learns them as
|
|
62
|
+
background.
|
|
63
|
+
- Images you haven't annotated yet are **left out**. If they were included, YOLO would
|
|
64
|
+
treat them as "nothing here".
|
|
65
|
+
- `train` and `val` point at the same images. Split them yourself for a real evaluation.
|
|
66
|
+
- If you move the export folder, update `path:` in `data.yaml`.
|
|
47
67
|
|
|
48
68
|
## Importing annotations
|
|
49
69
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Lannotex: offline-first image annotation."""
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.1"
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
import csv
|
|
4
4
|
import json
|
|
5
|
+
import os
|
|
5
6
|
from pathlib import Path
|
|
7
|
+
import shutil
|
|
6
8
|
import tempfile
|
|
7
9
|
import uuid
|
|
8
10
|
from . import __version__
|
|
@@ -76,6 +78,19 @@ def coco_record(image, annotations, labels):
|
|
|
76
78
|
return {'images':[image],'categories':[{'id':i+1,'name':s} for i,s in enumerate(labels)],'annotations':output}
|
|
77
79
|
|
|
78
80
|
|
|
81
|
+
def link_or_copy(source, target):
|
|
82
|
+
try: os.link(source, target)
|
|
83
|
+
except OSError: shutil.copy2(source, target)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def yolo_data_yaml(root, labels):
|
|
87
|
+
names = '\n'.join(f' {i}: {json.dumps(name)}' for i, name in enumerate(labels))
|
|
88
|
+
return ('# Ultralytics YOLO dataset exported by Lannotex.\n'
|
|
89
|
+
'# Train: yolo detect train data=data.yaml model=yolo11n.pt\n'
|
|
90
|
+
"# Update 'path' if you move this folder. train and val use the same images; split them for real evaluation.\n"
|
|
91
|
+
f'path: {json.dumps(str(root))}\ntrain: images\nval: images\nnames:\n{names}\n')
|
|
92
|
+
|
|
93
|
+
|
|
79
94
|
def export_project(root, destination, format_name, cancel, progress):
|
|
80
95
|
"""Stream a consistent DB snapshot; publish the completed directory by rename."""
|
|
81
96
|
project = Project(root,create=False)
|
|
@@ -87,6 +102,8 @@ def export_project(root, destination, format_name, cancel, progress):
|
|
|
87
102
|
try:
|
|
88
103
|
labels = [r['name'] for r in project.labels()]
|
|
89
104
|
atomic_text(output/'classes.json',json.dumps(labels,indent=2))
|
|
105
|
+
yolo = format_name == 'YOLO boxes'
|
|
106
|
+
if yolo: (output/'images').mkdir(); (output/'labels').mkdir()
|
|
90
107
|
# Spool COCO arrays to disk, rather than keeping all annotations in RAM.
|
|
91
108
|
with (output/'images.jsonl').open('w',encoding='utf-8') as ims, (output/'annotations.jsonl').open('w',encoding='utf-8') as anns, (output/'classification.csv').open('w',encoding='utf-8',newline='') as csvfile:
|
|
92
109
|
writer = csv.writer(csvfile); writer.writerow(['image_id','image_path','status',*labels])
|
|
@@ -104,6 +121,10 @@ def export_project(root, destination, format_name, cancel, progress):
|
|
|
104
121
|
summary['skipped'] += 1
|
|
105
122
|
if len(summary['errors'])<100: summary['errors'].append(f"{row['path']}: {'; '.join(errors)}")
|
|
106
123
|
continue
|
|
124
|
+
if yolo and row['status']=='new':
|
|
125
|
+
# YOLO reads an empty label file as "no objects"; never-annotated images would become false negatives.
|
|
126
|
+
summary['unlabeled'] += 1
|
|
127
|
+
continue
|
|
107
128
|
summary['included'] += 1
|
|
108
129
|
if row['status']=='new': summary['unlabeled'] += 1
|
|
109
130
|
name = f"{row['id']:08d}_{Path(row['path']).stem}"
|
|
@@ -112,9 +133,11 @@ def export_project(root, destination, format_name, cancel, progress):
|
|
|
112
133
|
ims.write(json.dumps(image)+'\n')
|
|
113
134
|
if format_name == 'Labelme JSON':
|
|
114
135
|
atomic_text(output/(name+'.json'),json.dumps(labelme_data(source,row['width'],row['height'],annotations,negative),indent=2))
|
|
115
|
-
elif
|
|
136
|
+
elif yolo:
|
|
116
137
|
lines = [yolo_line(a,labels.index(a.label)) for a in annotations if a.kind=='box']
|
|
117
|
-
|
|
138
|
+
source_path = project.resolve(row['path'])
|
|
139
|
+
link_or_copy(source_path, output/'images'/(name+source_path.suffix.lower()))
|
|
140
|
+
atomic_text(output/'labels'/(name+'.txt'),'\n'.join(lines)+ ('\n' if lines else ''))
|
|
118
141
|
summary['unsupported_annotations'] += sum(a.kind!='box' for a in annotations)
|
|
119
142
|
elif format_name == 'COCO JSON':
|
|
120
143
|
record = coco_record(image,annotations,labels)
|
|
@@ -138,9 +161,11 @@ def export_project(root, destination, format_name, cancel, progress):
|
|
|
138
161
|
stream.write(line.strip()); first=False
|
|
139
162
|
stream.write(sep)
|
|
140
163
|
stream.write(json.dumps([{'id':i+1,'name':s} for i,s in enumerate(labels)])+'}')
|
|
141
|
-
|
|
164
|
+
policy = 'Hard-linked or copied into images/' if yolo else 'Referenced, not copied'
|
|
165
|
+
atomic_text(output/'manifest.json',json.dumps({'version':__version__,'format_version':1,'editable_local_export':True,'image_policy':policy,'summary':summary},indent=2))
|
|
142
166
|
if not summary.get('cancelled'):
|
|
143
167
|
final = destination/('Lannotex-export-'+uuid.uuid4().hex[:8]); output.rename(final); output=final
|
|
168
|
+
if yolo: atomic_text(output/'data.yaml', yolo_data_yaml(output.resolve(), labels))
|
|
144
169
|
summary['path'] = str(output)
|
|
145
170
|
return summary
|
|
146
171
|
finally:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lannotex
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Fast, offline-first image annotation for classifications, boxes, and polygons.
|
|
5
5
|
Author: Lourdhu Raju
|
|
6
6
|
Maintainer: Lourdhu Raju
|
|
@@ -133,7 +133,7 @@ Every shortcut can be remapped. See the [annotating guide](https://lourdhu02.git
|
|
|
133
133
|
| **Annotate** | Bounding boxes · polygons with vertex drag, insert, and delete · multi-label image classes · "no target" negatives · 100-step undo/redo · zoom under the pointer, pan, arrow-key nudging · focus mode · light and dark themes |
|
|
134
134
|
| **Stay safe** | SQLite projects with versioned migrations · recovery drafts and 700 ms autosave · transactional saves · geometry validation · backup and restore · relinking for moved images · EXIF orientation handled |
|
|
135
135
|
| **Scale** | Virtualized image list (at most 1,024 records in memory) · background import and decoding · 96 MB preview cache · coordinates normalized to the original resolution |
|
|
136
|
-
| **Export** | Labelme JSON · YOLO · COCO JSON · classification CSV · lossless native JSONL · each export reports anything it skipped |
|
|
136
|
+
| **Export** | Labelme JSON · ready-to-train YOLO (`images/`, `labels/`, `data.yaml`) · COCO JSON · classification CSV · lossless native JSONL · each export reports anything it skipped |
|
|
137
137
|
| **Automate** | `lannotex export` and `lannotex analyze` run headless in CI, with no display needed |
|
|
138
138
|
| **Collaborate** | Optional LAN host with per-reviewer tokens, background sync, revision conflicts you resolve explicitly, revocation, and an audit history |
|
|
139
139
|
|
|
@@ -181,8 +181,8 @@ hashes. There is no telemetry, no external AI service, and no automatic upload.
|
|
|
181
181
|
|
|
182
182
|
## Roadmap
|
|
183
183
|
|
|
184
|
-
Next up:
|
|
185
|
-
|
|
184
|
+
Next up: more LabelMe shape types (points, lines, circles), VOC and YOLO-seg export, and
|
|
185
|
+
number-key label selection. See the
|
|
186
186
|
[roadmap](https://lourdhu02.github.io/Lannotex/ROADMAP/) and the
|
|
187
187
|
[measured performance](https://lourdhu02.github.io/Lannotex/PERFORMANCE/).
|
|
188
188
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"""Exporter primitives and headless project export."""
|
|
2
|
+
import json
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
import threading
|
|
4
5
|
|
|
6
|
+
from PIL import Image
|
|
7
|
+
|
|
5
8
|
from lannotex import __version__
|
|
6
9
|
from lannotex.domain import Annotation
|
|
7
10
|
from lannotex.exporters import coco_record, export_project, labelme_data, parse_labelme, yolo_line
|
|
@@ -61,3 +64,27 @@ def test_export_project_empty_creates_manifest(tmp_path):
|
|
|
61
64
|
assert (out / "classes.json").exists()
|
|
62
65
|
assert (out / "manifest.json").exists()
|
|
63
66
|
assert (out / "images.jsonl").exists()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_yolo_export_is_ultralytics_ready(tmp_path):
|
|
70
|
+
src = tmp_path / "imgs"; src.mkdir()
|
|
71
|
+
Image.new("RGB", (200, 100)).save(src / "a.png")
|
|
72
|
+
Image.new("RGB", (50, 50)).save(src / "b.jpg")
|
|
73
|
+
Image.new("RGB", (50, 50)).save(src / "c.png")
|
|
74
|
+
root = tmp_path / "project"; p = Project(root); p.add_label("cat"); p.add_label("dog")
|
|
75
|
+
a = p.add_image(src / "a.png", 200, 100); b = p.add_image(src / "b.jpg", 50, 50); p.add_image(src / "c.png", 50, 50)
|
|
76
|
+
p.save_annotations(a, [Annotation("dog", "box", [.1, .2, .5, .6]), Annotation("cat", "polygon", [[0, 0], [1, 0], [1, 1]])])
|
|
77
|
+
p.save_annotations(b, [], negative=True)
|
|
78
|
+
p.close()
|
|
79
|
+
|
|
80
|
+
result = export_project(root, tmp_path / "out", "YOLO boxes", threading.Event(), lambda *args: None)
|
|
81
|
+
out = Path(result["path"])
|
|
82
|
+
assert result["included"] == 2 and result["unlabeled"] == 1 and result["unsupported_annotations"] == 1
|
|
83
|
+
images = sorted(f.name for f in (out / "images").iterdir())
|
|
84
|
+
labels = sorted(f.name for f in (out / "labels").iterdir())
|
|
85
|
+
assert [Path(n).stem for n in images] == [Path(n).stem for n in labels] and len(images) == 2
|
|
86
|
+
assert (out / "labels" / labels[0]).read_text() == "1 0.300000 0.400000 0.400000 0.400000\n"
|
|
87
|
+
assert (out / "labels" / labels[1]).read_text() == ""
|
|
88
|
+
data = (out / "data.yaml").read_text()
|
|
89
|
+
assert "train: images" in data and '0: "cat"' in data and '1: "dog"' in data
|
|
90
|
+
assert json.loads(data.split("path: ")[1].splitlines()[0]) == str(out.resolve())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|