lours 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.
- lours-0.1.0/LICENSE +21 -0
- lours-0.1.0/PKG-INFO +115 -0
- lours-0.1.0/README.md +76 -0
- lours-0.1.0/lours/__init__.py +7 -0
- lours-0.1.0/lours/cli/__init__.py +0 -0
- lours-0.1.0/lours/cli/caipy_to_fiftyone.py +319 -0
- lours-0.1.0/lours/dataset/__init__.py +33 -0
- lours-0.1.0/lours/dataset/dataset.py +5388 -0
- lours-0.1.0/lours/dataset/indexing.py +122 -0
- lours-0.1.0/lours/dataset/io/__init__.py +0 -0
- lours-0.1.0/lours/dataset/io/caipy.py +695 -0
- lours-0.1.0/lours/dataset/io/coco.py +335 -0
- lours-0.1.0/lours/dataset/io/common.py +280 -0
- lours-0.1.0/lours/dataset/io/crowd_human.py +134 -0
- lours-0.1.0/lours/dataset/io/darknet.py +736 -0
- lours-0.1.0/lours/dataset/io/images_folder.py +113 -0
- lours-0.1.0/lours/dataset/io/mot.py +126 -0
- lours-0.1.0/lours/dataset/io/parquet.py +37 -0
- lours-0.1.0/lours/dataset/io/pascalvoc.py +252 -0
- lours-0.1.0/lours/dataset/io/schema_util/__init__.py +21 -0
- lours-0.1.0/lours/dataset/io/schema_util/default-schema.json +61 -0
- lours-0.1.0/lours/dataset/io/schema_util/schema_util_functions.py +326 -0
- lours-0.1.0/lours/dataset/merge.py +221 -0
- lours-0.1.0/lours/dataset/remap_presets/__init__.py +53 -0
- lours-0.1.0/lours/dataset/remap_presets/coco_to_pascalvoc.csv +21 -0
- lours-0.1.0/lours/dataset/remap_presets/coco_to_supercategory.csv +92 -0
- lours-0.1.0/lours/dataset/split/balanced_groups.py +340 -0
- lours-0.1.0/lours/dataset/split/dataset_splitter.py +473 -0
- lours-0.1.0/lours/dataset/split/disjoint_groups.py +236 -0
- lours-0.1.0/lours/evaluation/__init__.py +4 -0
- lours-0.1.0/lours/evaluation/detection/__init__.py +7 -0
- lours-0.1.0/lours/evaluation/detection/crowd_detection_evaluator.py +248 -0
- lours-0.1.0/lours/evaluation/detection/detection_evaluator.py +701 -0
- lours-0.1.0/lours/evaluation/detection/detection_evaluator_base.py +100 -0
- lours-0.1.0/lours/evaluation/detection/util.py +450 -0
- lours-0.1.0/lours/evaluation/evaluator.py +313 -0
- lours-0.1.0/lours/utils/__init__.py +38 -0
- lours-0.1.0/lours/utils/annotations_appender.py +375 -0
- lours-0.1.0/lours/utils/bbox_converter.py +496 -0
- lours-0.1.0/lours/utils/column_booleanizer.py +255 -0
- lours-0.1.0/lours/utils/dataframe_formatter.py +71 -0
- lours-0.1.0/lours/utils/difftools.py +186 -0
- lours-0.1.0/lours/utils/doc_utils.py +746 -0
- lours-0.1.0/lours/utils/fiftyone_convert.py +333 -0
- lours-0.1.0/lours/utils/grouper.py +361 -0
- lours-0.1.0/lours/utils/label_map_merger.py +69 -0
- lours-0.1.0/lours/utils/notebook_utils.py +186 -0
- lours-0.1.0/lours/utils/parquet_saver.py +117 -0
- lours-0.1.0/lours/utils/regression_testing.py +171 -0
- lours-0.1.0/lours/utils/testing.py +642 -0
- lours-0.1.0/pyproject.toml +108 -0
lours-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 XXII-AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
lours-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: lours
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Lilian Glaudin
|
|
7
|
+
Author-email: lilian.glaudin@xxii.fr
|
|
8
|
+
Requires-Python: >=3.10,<3.13
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Provides-Extra: fiftyone
|
|
14
|
+
Provides-Extra: plot-utils
|
|
15
|
+
Provides-Extra: regression
|
|
16
|
+
Requires-Dist: POT (>=0.9.1,<0.10.0)
|
|
17
|
+
Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
|
|
18
|
+
Requires-Dist: boto3 (==1.26.83) ; extra == "fiftyone"
|
|
19
|
+
Requires-Dist: faker (>=22.6.0,<23.0.0)
|
|
20
|
+
Requires-Dist: fiftyone (>=0.23,<0.24) ; extra == "fiftyone"
|
|
21
|
+
Requires-Dist: fiftyone-db-ubuntu2204 (>=0.4.0,<0.5.0) ; sys_platform == "linux"
|
|
22
|
+
Requires-Dist: imageio (>=2.31.3,<3.0.0)
|
|
23
|
+
Requires-Dist: imagesize (>=1.4.1,<2.0.0)
|
|
24
|
+
Requires-Dist: jsonschema-rs (>=0.16.3,<0.17.0)
|
|
25
|
+
Requires-Dist: numpy (>=1,<2)
|
|
26
|
+
Requires-Dist: pandas (>=2.2.2,<3.0.0)
|
|
27
|
+
Requires-Dist: pyarrow (>=16.1.0,<17.0.0)
|
|
28
|
+
Requires-Dist: pytest-regressions (>=2.5.0,<3.0.0) ; extra == "regression"
|
|
29
|
+
Requires-Dist: scikit-learn (>=1.3.0,<2.0.0)
|
|
30
|
+
Requires-Dist: scipy (>=1,<1.14)
|
|
31
|
+
Requires-Dist: shortuuid (>=1.0.11,<2.0.0)
|
|
32
|
+
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
33
|
+
Requires-Dist: tqdm (>=4.64,<5.0)
|
|
34
|
+
Requires-Dist: typing-extensions (>=4.7.1,<5.0.0)
|
|
35
|
+
Requires-Dist: watchdog (>=3.0.0,<4.0.0)
|
|
36
|
+
Requires-Dist: xmltodict (>=0.13.0,<0.14.0)
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# README LOURS
|
|
40
|
+
|
|
41
|
+
A lib to help R&D team with its experiments.
|
|
42
|
+
|
|
43
|
+
## DOC API
|
|
44
|
+
|
|
45
|
+
Sphinx docs is available here:
|
|
46
|
+
|
|
47
|
+
- [stable](UPDATE-ME)
|
|
48
|
+
- [latest](UPDATE-ME)
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
### From Pypi repository
|
|
53
|
+
|
|
54
|
+
### With poetry
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
poetry add lours
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
You can also install the pre-release by modifying the last line
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
poetry add lours --alow-prereleases
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### With pip
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install lours
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
You can also install the pre-release by adding the `--pre` option
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install lours --pre
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### From source
|
|
79
|
+
|
|
80
|
+
- `pip`
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install -e . # Only for pip > 21.3
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- `poetry >= 1.2`
|
|
87
|
+
|
|
88
|
+
Assuming both lours and your project are in the same folder, adapt the relative path of the repo if needed
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
poetry add --editable ../lours/
|
|
92
|
+
poetry add --editable https://github.com/XXII-AI/lours.git
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## The dataset object
|
|
96
|
+
|
|
97
|
+
See <https://UPDATE-ME>
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from lours.dataset import from_caipy, from_coco
|
|
103
|
+
dataset1 = from_caipy("path/to/caipy")
|
|
104
|
+
print(dataset1)
|
|
105
|
+
dataset2 = from_coco("path/to/coco", images_root="/path/to/coco_images")
|
|
106
|
+
dataset2 = dataset2.remap_from_preset("coco", "pascalvoc")
|
|
107
|
+
print(dataset2)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Tutorials
|
|
111
|
+
|
|
112
|
+
See some notebooks in folder `docs/notebooks`
|
|
113
|
+
|
|
114
|
+
Alternatively, See <UPDATE-ME/stable/tutorials>
|
|
115
|
+
|
lours-0.1.0/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# README LOURS
|
|
2
|
+
|
|
3
|
+
A lib to help R&D team with its experiments.
|
|
4
|
+
|
|
5
|
+
## DOC API
|
|
6
|
+
|
|
7
|
+
Sphinx docs is available here:
|
|
8
|
+
|
|
9
|
+
- [stable](UPDATE-ME)
|
|
10
|
+
- [latest](UPDATE-ME)
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
### From Pypi repository
|
|
15
|
+
|
|
16
|
+
### With poetry
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
poetry add lours
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
You can also install the pre-release by modifying the last line
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
poetry add lours --alow-prereleases
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### With pip
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install lours
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
You can also install the pre-release by adding the `--pre` option
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install lours --pre
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### From source
|
|
41
|
+
|
|
42
|
+
- `pip`
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e . # Only for pip > 21.3
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- `poetry >= 1.2`
|
|
49
|
+
|
|
50
|
+
Assuming both lours and your project are in the same folder, adapt the relative path of the repo if needed
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
poetry add --editable ../lours/
|
|
54
|
+
poetry add --editable https://github.com/XXII-AI/lours.git
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## The dataset object
|
|
58
|
+
|
|
59
|
+
See <https://UPDATE-ME>
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from lours.dataset import from_caipy, from_coco
|
|
65
|
+
dataset1 = from_caipy("path/to/caipy")
|
|
66
|
+
print(dataset1)
|
|
67
|
+
dataset2 = from_coco("path/to/coco", images_root="/path/to/coco_images")
|
|
68
|
+
dataset2 = dataset2.remap_from_preset("coco", "pascalvoc")
|
|
69
|
+
print(dataset2)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Tutorials
|
|
73
|
+
|
|
74
|
+
See some notebooks in folder `docs/notebooks`
|
|
75
|
+
|
|
76
|
+
Alternatively, See <UPDATE-ME/stable/tutorials>
|
|
File without changes
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import socketserver
|
|
2
|
+
from argparse import ArgumentParser
|
|
3
|
+
from logging import warn
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from time import sleep
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from watchdog.events import (
|
|
9
|
+
EVENT_TYPE_CLOSED,
|
|
10
|
+
EVENT_TYPE_OPENED,
|
|
11
|
+
FileSystemEvent,
|
|
12
|
+
FileSystemEventHandler,
|
|
13
|
+
)
|
|
14
|
+
from watchdog.observers import Observer
|
|
15
|
+
from watchdog.observers.api import EventQueue
|
|
16
|
+
|
|
17
|
+
from lours.dataset import Dataset, from_caipy, from_caipy_generic
|
|
18
|
+
from lours.utils import try_import_fiftyone
|
|
19
|
+
from lours.utils.difftools import dataset_diff
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
import fiftyone as fo
|
|
23
|
+
else:
|
|
24
|
+
fo = try_import_fiftyone()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DatasetUpdateHandler(FileSystemEventHandler):
|
|
28
|
+
"""Class to update the dataset in fiftyone each time a change is detected"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
dataset: Dataset,
|
|
33
|
+
fo_dataset: fo.Dataset,
|
|
34
|
+
images_root: Path,
|
|
35
|
+
annotations_root: Path,
|
|
36
|
+
splits_to_read: list[str],
|
|
37
|
+
event_queue: EventQueue,
|
|
38
|
+
cooldown_time: float = 1,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Constructor of DatasetUpdateHandler
|
|
41
|
+
|
|
42
|
+
The handler will wait for a cooldown period of time and then will cancel all
|
|
43
|
+
changes detected by watchdog in the mean time. This allows to only run the
|
|
44
|
+
dataset update once when multiple files are modified at the same time.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
dataset: dataset object to compare the newly loaded caipy dataset with,
|
|
48
|
+
should have already been converted to fiftyone
|
|
49
|
+
fo_dataset: fiftyone dataset object corresponding to the lours dataset.
|
|
50
|
+
will be used to remove modified or deleted data from the caipy folder.
|
|
51
|
+
images_root: parameter given to caipy loading function, should be the same
|
|
52
|
+
as the one used when constructing the already existing dataset
|
|
53
|
+
annotations_root: parameter given to caipy loading function, should be the
|
|
54
|
+
same as the one used when constructing the already existing dataset
|
|
55
|
+
splits_to_read: parameter given to caipy loading function, should be the
|
|
56
|
+
same as the one used when constructing the already existing dataset
|
|
57
|
+
event_queue: queue object to know if other changes have been detected after
|
|
58
|
+
the cooldown
|
|
59
|
+
cooldown_time: time in seconds between a detected change and a dataset
|
|
60
|
+
update. Every change event between first event and the cooldown will be
|
|
61
|
+
ignored. Useful when adding multiple files at once. Defaults to 1
|
|
62
|
+
"""
|
|
63
|
+
super().__init__()
|
|
64
|
+
self.dataset = dataset
|
|
65
|
+
self.fo_dataset = fo_dataset
|
|
66
|
+
self.images_root = images_root
|
|
67
|
+
self.annotations_root = annotations_root
|
|
68
|
+
self.splits_to_read = splits_to_read
|
|
69
|
+
self.cooldown_time = cooldown_time
|
|
70
|
+
self.event_queue = event_queue
|
|
71
|
+
|
|
72
|
+
def update_dataset(self) -> None:
|
|
73
|
+
"""Compare the dataset with the one loaded from caipy.
|
|
74
|
+
|
|
75
|
+
If different, add the new elements to fiftyone, but only them.
|
|
76
|
+
"""
|
|
77
|
+
caipy_dataset = from_caipy_generic(
|
|
78
|
+
images_folder=self.images_root,
|
|
79
|
+
annotations_folder=self.annotations_root,
|
|
80
|
+
splits_to_read=self.splits_to_read,
|
|
81
|
+
)
|
|
82
|
+
to_update, to_remove, common = dataset_diff(caipy_dataset, self.dataset)
|
|
83
|
+
if len(to_remove) > 0:
|
|
84
|
+
annotations_filter = fo.ViewField("lours_id").is_in(
|
|
85
|
+
common.annotations.index
|
|
86
|
+
)
|
|
87
|
+
images_filter = fo.ViewField("lours_id").is_in(common.images.index)
|
|
88
|
+
|
|
89
|
+
filtered_view = self.fo_dataset.match(images_filter)
|
|
90
|
+
if "groundtruth_detection" in self.fo_dataset.get_field_schema():
|
|
91
|
+
filtered_view = filtered_view.filter_labels(
|
|
92
|
+
"groundtruth_detection.detections", annotations_filter
|
|
93
|
+
)
|
|
94
|
+
if "groundtruth_keypoints" in self.fo_dataset.get_field_schema():
|
|
95
|
+
filtered_view = filtered_view.filter_labels(
|
|
96
|
+
"groundtruth_keypoint.keypoints", annotations_filter
|
|
97
|
+
)
|
|
98
|
+
# Register this view on top of the existing dataset
|
|
99
|
+
filtered_view.save()
|
|
100
|
+
filtered_view.keep()
|
|
101
|
+
filtered_view.keep_fields()
|
|
102
|
+
|
|
103
|
+
if len(to_update) > 0:
|
|
104
|
+
self.fo_dataset = to_update.to_fiftyone(
|
|
105
|
+
dataset_name=str(self.fo_dataset.name), existing="update"
|
|
106
|
+
)
|
|
107
|
+
self.fo_dataset.save()
|
|
108
|
+
self.dataset = caipy_dataset
|
|
109
|
+
|
|
110
|
+
def on_any_event(self, event: FileSystemEvent) -> None:
|
|
111
|
+
"""Method called when a file or folder is created, modified or deleted.
|
|
112
|
+
|
|
113
|
+
If after a cooldown poeriod, the event queue has elements, this will remove
|
|
114
|
+
them from the queue so that the dataset update is not called again. Since the
|
|
115
|
+
dataset update reads the whole folder, the subsequent detected changes will not
|
|
116
|
+
be ignored even though they are removed from the event queue.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
event: event describing the file or folder that was created.
|
|
120
|
+
Not used today, but could probably be used in the future to avoid
|
|
121
|
+
unnecessary reloading of caipy dataset
|
|
122
|
+
"""
|
|
123
|
+
if event.event_type in [EVENT_TYPE_CLOSED, EVENT_TYPE_OPENED]:
|
|
124
|
+
return
|
|
125
|
+
sleep(self.cooldown_time)
|
|
126
|
+
while not self.event_queue.empty():
|
|
127
|
+
self.event_queue.get()
|
|
128
|
+
self.update_dataset()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_argparser() -> ArgumentParser:
|
|
132
|
+
"""Function to get the argparser, which will be parsed itself by sphinx-argparse"""
|
|
133
|
+
parser = ArgumentParser(
|
|
134
|
+
description=(
|
|
135
|
+
"Directly convert a Caipy folder into fiftyone for quick inspection"
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
i_parser = parser.add_argument_group("Input arguments")
|
|
140
|
+
i_parser.add_argument(
|
|
141
|
+
"--mode",
|
|
142
|
+
choices=["vanilla", "generic"],
|
|
143
|
+
default="vanilla",
|
|
144
|
+
help=(
|
|
145
|
+
"Choice between vanilla mode, where a single folder is given with option"
|
|
146
|
+
" ``--input_folder``, and generic, where two folders are given with options"
|
|
147
|
+
" ``--images_root`` and ``--annotations_root``"
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
i_parser.add_argument(
|
|
151
|
+
"--input-folder",
|
|
152
|
+
"-i",
|
|
153
|
+
help="When in vanilla mode, folder where the CAIPY dataset is stored",
|
|
154
|
+
type=Path,
|
|
155
|
+
default=None,
|
|
156
|
+
)
|
|
157
|
+
i_parser.add_argument(
|
|
158
|
+
"--images_root",
|
|
159
|
+
"--ir",
|
|
160
|
+
help=(
|
|
161
|
+
"When in generic mode, folder where images are stored. Equivalent folder in"
|
|
162
|
+
" vanilla mode is ``dataset/Images``"
|
|
163
|
+
),
|
|
164
|
+
type=Path,
|
|
165
|
+
default=None,
|
|
166
|
+
)
|
|
167
|
+
i_parser.add_argument(
|
|
168
|
+
"--annotations_root",
|
|
169
|
+
"--ar",
|
|
170
|
+
help=(
|
|
171
|
+
"When in generic mode, folder where annotations json files are stored."
|
|
172
|
+
" Equivalent folder in vanilla mode is ``dataset/Annotations``"
|
|
173
|
+
),
|
|
174
|
+
type=Path,
|
|
175
|
+
default=None,
|
|
176
|
+
)
|
|
177
|
+
i_parser.add_argument(
|
|
178
|
+
"--splits-to-read",
|
|
179
|
+
"-s",
|
|
180
|
+
nargs="*",
|
|
181
|
+
default=None,
|
|
182
|
+
help=(
|
|
183
|
+
"Optional list of splits to read. If not selected, will read all splits and"
|
|
184
|
+
" convert them to fiftyone"
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
f_parser = parser.add_argument_group("Fiftyone arguments")
|
|
188
|
+
f_parser.add_argument(
|
|
189
|
+
"--dataset-name",
|
|
190
|
+
"--name",
|
|
191
|
+
"-n",
|
|
192
|
+
help=(
|
|
193
|
+
"Optional dataset name to appear in fiftyone app. If not selected, will"
|
|
194
|
+
" take the name of loaded dataset object, i.e. the name of ``input-folder``"
|
|
195
|
+
" or parent of ``--images-root`` without its parents"
|
|
196
|
+
),
|
|
197
|
+
default=None,
|
|
198
|
+
)
|
|
199
|
+
f_parser.add_argument(
|
|
200
|
+
"--not-persistent",
|
|
201
|
+
"--np",
|
|
202
|
+
action="store_false",
|
|
203
|
+
dest="persistent",
|
|
204
|
+
help=(
|
|
205
|
+
"If selected, will not save the converted dataset in fiftyone. It will be "
|
|
206
|
+
"available to inspect with the browser during the time this script is"
|
|
207
|
+
"running, but will be removed as soon as the script is stopped."
|
|
208
|
+
),
|
|
209
|
+
)
|
|
210
|
+
f_parser.add_argument(
|
|
211
|
+
"--no-app",
|
|
212
|
+
"--na",
|
|
213
|
+
action="store_false",
|
|
214
|
+
dest="launch_app",
|
|
215
|
+
help=(
|
|
216
|
+
"If selected, will not launch the app. This needs the 'persistent' option"
|
|
217
|
+
" to be selected for the command to do anything"
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
f_parser.add_argument(
|
|
221
|
+
"--watch",
|
|
222
|
+
"-w",
|
|
223
|
+
action="store_true",
|
|
224
|
+
help=(
|
|
225
|
+
"If selected, will watch the folder containing the dataset. Each time a"
|
|
226
|
+
" change in the folder is detected, the dataset is updated and so is the"
|
|
227
|
+
" fiftyone counterpart. Useful when you are constructing a dataset"
|
|
228
|
+
" gradually. Note that this option is useless if the app is not launched"
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
f_parser.add_argument(
|
|
232
|
+
"--port",
|
|
233
|
+
"-p",
|
|
234
|
+
default=0,
|
|
235
|
+
type=int,
|
|
236
|
+
help=(
|
|
237
|
+
"Server port to connect to the app server. If not set, will choose randomly"
|
|
238
|
+
" a free port"
|
|
239
|
+
),
|
|
240
|
+
)
|
|
241
|
+
f_parser.add_argument(
|
|
242
|
+
"--only-local",
|
|
243
|
+
action="store_true",
|
|
244
|
+
help=(
|
|
245
|
+
"if selected, will not open the server for other than localhost. the server"
|
|
246
|
+
" can still be reachable with ssh tunnelling though"
|
|
247
|
+
),
|
|
248
|
+
)
|
|
249
|
+
return parser
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def run():
|
|
253
|
+
"""CLI function for caipy to fiftyone"""
|
|
254
|
+
parser = get_argparser()
|
|
255
|
+
|
|
256
|
+
args = parser.parse_args()
|
|
257
|
+
|
|
258
|
+
if not args.launch_app and not args.persistent:
|
|
259
|
+
warn(
|
|
260
|
+
"App won't be launched and dataset is not persistent, this command will not"
|
|
261
|
+
" do anything",
|
|
262
|
+
RuntimeWarning,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if args.mode == "vanilla":
|
|
266
|
+
assert (
|
|
267
|
+
args.input_folder is not None
|
|
268
|
+
), "You must provide a path to input_folder for vanilla mode"
|
|
269
|
+
annotations_root = args.input_folder / "Annotations"
|
|
270
|
+
images_root = args.input_folder / "Images"
|
|
271
|
+
dataset = from_caipy(args.input_folder, splits_to_read=args.splits_to_read)
|
|
272
|
+
else:
|
|
273
|
+
assert args.images_root is not None and args.annotations_root is not None, (
|
|
274
|
+
"You must provide paths for both images_root and annotations_root for"
|
|
275
|
+
" generic mode"
|
|
276
|
+
)
|
|
277
|
+
images_root = args.images_root
|
|
278
|
+
annotations_root = args.annotations_root
|
|
279
|
+
dataset = from_caipy_generic(
|
|
280
|
+
images_folder=args.images_root,
|
|
281
|
+
annotations_folder=args.annotations_root,
|
|
282
|
+
splits_to_read=args.splits_to_read,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
dataset_name = (
|
|
286
|
+
args.dataset_name if args.dataset_name is not None else dataset.dataset_name
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
fo_dataset = dataset.to_fiftyone(dataset_name=dataset_name)
|
|
290
|
+
if args.persistent:
|
|
291
|
+
fo_dataset.persistent = True
|
|
292
|
+
|
|
293
|
+
if args.launch_app:
|
|
294
|
+
launch_kwargs = {}
|
|
295
|
+
if not args.only_local:
|
|
296
|
+
launch_kwargs["address"] = "0.0.0.0"
|
|
297
|
+
if args.port == 0:
|
|
298
|
+
print("Getting a random free port ...")
|
|
299
|
+
# Stolen from https://stackoverflow.com/a/61685162
|
|
300
|
+
with socketserver.TCPServer(("localhost", 0), None) as s: # pyright: ignore
|
|
301
|
+
port = s.server_address[1]
|
|
302
|
+
print(f"Port chosen : {port}")
|
|
303
|
+
else:
|
|
304
|
+
port = args.port
|
|
305
|
+
launch_kwargs["port"] = port
|
|
306
|
+
session = fo.launch_app(dataset=fo_dataset, **launch_kwargs)
|
|
307
|
+
if args.watch:
|
|
308
|
+
observer = Observer()
|
|
309
|
+
event_handler = DatasetUpdateHandler(
|
|
310
|
+
dataset,
|
|
311
|
+
fo_dataset,
|
|
312
|
+
images_root,
|
|
313
|
+
annotations_root,
|
|
314
|
+
args.splits_to_read,
|
|
315
|
+
observer.event_queue,
|
|
316
|
+
)
|
|
317
|
+
observer.schedule(event_handler, str(annotations_root), recursive=True)
|
|
318
|
+
observer.start()
|
|
319
|
+
session.wait(-1)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from .dataset import Dataset
|
|
2
|
+
from .io.caipy import from_caipy, from_caipy_generic
|
|
3
|
+
from .io.coco import from_coco, from_coco_keypoints
|
|
4
|
+
from .io.crowd_human import from_crowd_human
|
|
5
|
+
from .io.darknet import (
|
|
6
|
+
from_darknet,
|
|
7
|
+
from_darknet_generic,
|
|
8
|
+
from_darknet_json,
|
|
9
|
+
from_darknet_yolov5,
|
|
10
|
+
)
|
|
11
|
+
from .io.images_folder import from_files, from_folder
|
|
12
|
+
from .io.mot import from_mot
|
|
13
|
+
from .io.parquet import from_parquet
|
|
14
|
+
from .io.pascalvoc import from_pascalVOC_detection, from_pascalVOC_generic
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Dataset",
|
|
18
|
+
"from_folder",
|
|
19
|
+
"from_files",
|
|
20
|
+
"from_caipy",
|
|
21
|
+
"from_caipy_generic",
|
|
22
|
+
"from_coco",
|
|
23
|
+
"from_coco_keypoints",
|
|
24
|
+
"from_darknet",
|
|
25
|
+
"from_darknet_yolov5",
|
|
26
|
+
"from_darknet_generic",
|
|
27
|
+
"from_darknet_json",
|
|
28
|
+
"from_crowd_human",
|
|
29
|
+
"from_mot",
|
|
30
|
+
"from_parquet",
|
|
31
|
+
"from_pascalVOC_detection",
|
|
32
|
+
"from_pascalVOC_generic",
|
|
33
|
+
]
|