rfcli 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.
- rfcli-0.1.0/PKG-INFO +18 -0
- rfcli-0.1.0/README.md +0 -0
- rfcli-0.1.0/rfcli/__init__.py +0 -0
- rfcli-0.1.0/rfcli/config.py +9 -0
- rfcli-0.1.0/rfcli/convert.py +51 -0
- rfcli-0.1.0/rfcli/infer.py +0 -0
- rfcli-0.1.0/rfcli/main.py +70 -0
- rfcli-0.1.0/rfcli/predict.py +26 -0
- rfcli-0.1.0/rfcli/train.py +0 -0
- rfcli-0.1.0/rfcli/train_local.py +28 -0
- rfcli-0.1.0/rfcli/upload.py +147 -0
- rfcli-0.1.0/rfcli/utils.py +0 -0
- rfcli-0.1.0/rfcli/version.py +47 -0
- rfcli-0.1.0/rfcli.egg-info/PKG-INFO +18 -0
- rfcli-0.1.0/rfcli.egg-info/SOURCES.txt +19 -0
- rfcli-0.1.0/rfcli.egg-info/dependency_links.txt +1 -0
- rfcli-0.1.0/rfcli.egg-info/entry_points.txt +2 -0
- rfcli-0.1.0/rfcli.egg-info/requires.txt +8 -0
- rfcli-0.1.0/rfcli.egg-info/top_level.txt +1 -0
- rfcli-0.1.0/setup.cfg +4 -0
- rfcli-0.1.0/setup.py +25 -0
rfcli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rfcli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dataset-to-Deployment CLI for Object Detection
|
|
5
|
+
Author: Your Name
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Requires-Dist: click
|
|
8
|
+
Requires-Dist: rich
|
|
9
|
+
Requires-Dist: roboflow
|
|
10
|
+
Requires-Dist: ultralytics
|
|
11
|
+
Requires-Dist: fastapi
|
|
12
|
+
Requires-Dist: uvicorn
|
|
13
|
+
Requires-Dist: python-multipart
|
|
14
|
+
Requires-Dist: opencv-python
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: requires-dist
|
|
17
|
+
Dynamic: requires-python
|
|
18
|
+
Dynamic: summary
|
rfcli-0.1.0/README.md
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def coco_to_yolo(json_path, dataset_path):
|
|
6
|
+
|
|
7
|
+
labels_dir = os.path.join(dataset_path, "labels")
|
|
8
|
+
os.makedirs(labels_dir, exist_ok=True)
|
|
9
|
+
|
|
10
|
+
with open(json_path) as f:
|
|
11
|
+
data = json.load(f)
|
|
12
|
+
|
|
13
|
+
images = {img["id"]: img for img in data["images"]}
|
|
14
|
+
annotations = data["annotations"]
|
|
15
|
+
|
|
16
|
+
categories = data["categories"]
|
|
17
|
+
cat_map = {cat["id"]: i for i, cat in enumerate(categories)}
|
|
18
|
+
|
|
19
|
+
ann_by_image = {}
|
|
20
|
+
|
|
21
|
+
for ann in annotations:
|
|
22
|
+
ann_by_image.setdefault(ann["image_id"], []).append(ann)
|
|
23
|
+
|
|
24
|
+
for img_id, img_data in images.items():
|
|
25
|
+
|
|
26
|
+
file_name = img_data["file_name"]
|
|
27
|
+
width = float(img_data["width"])
|
|
28
|
+
height = float(img_data["height"])
|
|
29
|
+
|
|
30
|
+
label_path = os.path.join(
|
|
31
|
+
labels_dir,
|
|
32
|
+
os.path.splitext(file_name)[0] + ".txt"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
with open(label_path, "w") as f:
|
|
36
|
+
|
|
37
|
+
for ann in ann_by_image.get(img_id, []):
|
|
38
|
+
|
|
39
|
+
# 🔥 FIX: ensure numeric
|
|
40
|
+
x, y, w, h = map(float, ann["bbox"])
|
|
41
|
+
|
|
42
|
+
x_center = (x + w / 2) / width
|
|
43
|
+
y_center = (y + h / 2) / height
|
|
44
|
+
w /= width
|
|
45
|
+
h /= height
|
|
46
|
+
|
|
47
|
+
class_id = cat_map[ann["category_id"]]
|
|
48
|
+
|
|
49
|
+
f.write(f"{class_id} {x_center} {y_center} {w} {h}\n")
|
|
50
|
+
|
|
51
|
+
print("✅ COCO → YOLO conversion complete!")
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from rich import print
|
|
3
|
+
from rfcli.upload import upload_images, upload_dataset
|
|
4
|
+
from rfcli.version import create_version
|
|
5
|
+
from rfcli.train_local import train_local
|
|
6
|
+
from rfcli.predict import predict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def cli():
|
|
11
|
+
"""Roboflow CLI - Dataset to Deployment"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# 🔹 Single image upload
|
|
16
|
+
@cli.command()
|
|
17
|
+
@click.option('--workspace', required=True)
|
|
18
|
+
@click.option('--project', required=True)
|
|
19
|
+
@click.option('--folder', required=True)
|
|
20
|
+
def upload(workspace, project, folder):
|
|
21
|
+
print("[bold blue]🚀 Uploading images...[/bold blue]")
|
|
22
|
+
upload_images(workspace, project, folder)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# 🔥 Dataset upload
|
|
26
|
+
@cli.command(name="upload-dataset")
|
|
27
|
+
@click.option('--workspace', required=True)
|
|
28
|
+
@click.option('--project', required=True)
|
|
29
|
+
@click.option('--path', required=True)
|
|
30
|
+
def upload_dataset_cmd(workspace, project, path):
|
|
31
|
+
print("[bold blue]🚀 Uploading dataset...[/bold blue]")
|
|
32
|
+
upload_dataset(workspace, project, path)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# 🔥 Create version
|
|
36
|
+
@cli.command(name="create-version")
|
|
37
|
+
@click.option('--workspace', required=True)
|
|
38
|
+
@click.option('--project', required=True)
|
|
39
|
+
@click.option('--name', required=True)
|
|
40
|
+
def create_version_cmd(workspace, project, name):
|
|
41
|
+
print("[bold blue]🚀 Creating dataset version...[/bold blue]")
|
|
42
|
+
create_version(workspace, project, name)
|
|
43
|
+
|
|
44
|
+
#train local
|
|
45
|
+
@cli.command(name="train-local")
|
|
46
|
+
@click.option('--workspace', required=True)
|
|
47
|
+
@click.option('--project', required=True)
|
|
48
|
+
@click.option('--version', required=True, type=int)
|
|
49
|
+
def train_local_cmd(workspace, project, version):
|
|
50
|
+
train_local(workspace, project, version)
|
|
51
|
+
|
|
52
|
+
#predict
|
|
53
|
+
@cli.command(name="predict")
|
|
54
|
+
@click.option('--image', required=True)
|
|
55
|
+
def predict_cmd(image):
|
|
56
|
+
predict(image)
|
|
57
|
+
|
|
58
|
+
@click.group()
|
|
59
|
+
@click.version_option("0.1.0")
|
|
60
|
+
def cli():
|
|
61
|
+
"""Roboflow CLI - Dataset to Deployment"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@cli.command()
|
|
65
|
+
def hello():
|
|
66
|
+
print("[bold green]Roboflow CLI is working! 🚀[/bold green]")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
cli()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from ultralytics import YOLO
|
|
2
|
+
import cv2
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def predict(image_path):
|
|
7
|
+
|
|
8
|
+
print("🚀 Running inference...")
|
|
9
|
+
|
|
10
|
+
model = YOLO("runs/detect/train/weights/best.pt")
|
|
11
|
+
|
|
12
|
+
results = model(image_path)
|
|
13
|
+
|
|
14
|
+
for r in results:
|
|
15
|
+
boxes = r.boxes
|
|
16
|
+
|
|
17
|
+
for box in boxes:
|
|
18
|
+
print(f"Detected class: {int(box.cls[0])}, Confidence: {float(box.conf[0]):.2f}")
|
|
19
|
+
|
|
20
|
+
# 🔥 Save output instead of showing
|
|
21
|
+
annotated = results[0].plot()
|
|
22
|
+
|
|
23
|
+
output_path = "prediction.jpg"
|
|
24
|
+
cv2.imwrite(output_path, annotated)
|
|
25
|
+
|
|
26
|
+
print(f"\n✅ Prediction saved at: {os.path.abspath(output_path)}")
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from roboflow import Roboflow
|
|
2
|
+
from ultralytics import YOLO
|
|
3
|
+
from rfcli.config import ROBOFLOW_API_KEY
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def train_local(workspace, project, version):
|
|
7
|
+
|
|
8
|
+
print("🚀 Downloading dataset from Roboflow...")
|
|
9
|
+
|
|
10
|
+
rf = Roboflow(api_key=ROBOFLOW_API_KEY)
|
|
11
|
+
dataset = rf.workspace(workspace).project(project).version(version).download("yolov8")
|
|
12
|
+
|
|
13
|
+
print("✅ Dataset downloaded!")
|
|
14
|
+
|
|
15
|
+
print("🚀 Starting YOLO training...")
|
|
16
|
+
|
|
17
|
+
model = YOLO("yolov8n.pt")
|
|
18
|
+
|
|
19
|
+
model.train(
|
|
20
|
+
data=f"{dataset.location}/data.yaml",
|
|
21
|
+
epochs=50,
|
|
22
|
+
imgsz=640
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
print("✅ Training complete!")
|
|
26
|
+
|
|
27
|
+
print("📦 Best model saved at:")
|
|
28
|
+
print("runs/detect/train/weights/best.pt")
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from rich import print
|
|
3
|
+
from roboflow import Roboflow
|
|
4
|
+
from rfcli.config import ROBOFLOW_API_KEY
|
|
5
|
+
from rfcli.convert import coco_to_yolo
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# -----------------------------
|
|
9
|
+
# 🔹 SINGLE IMAGE UPLOAD
|
|
10
|
+
# -----------------------------
|
|
11
|
+
def upload_images(workspace, project_name, folder_path):
|
|
12
|
+
|
|
13
|
+
rf = Roboflow(api_key=ROBOFLOW_API_KEY)
|
|
14
|
+
project = rf.workspace(workspace).project(project_name)
|
|
15
|
+
|
|
16
|
+
images = [f for f in os.listdir(folder_path) if f.lower().endswith((".jpg", ".jpeg", ".png"))]
|
|
17
|
+
|
|
18
|
+
print(f"[blue]Uploading {len(images)} images...[/blue]")
|
|
19
|
+
|
|
20
|
+
for i, img_name in enumerate(images):
|
|
21
|
+
try:
|
|
22
|
+
project.upload(os.path.join(folder_path, img_name))
|
|
23
|
+
if i % 50 == 0:
|
|
24
|
+
print(f"[green]{i} uploaded...[/green]")
|
|
25
|
+
except Exception as e:
|
|
26
|
+
print(f"[red]Failed {img_name}: {e}[/red]")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# -----------------------------
|
|
30
|
+
# 🔥 MAIN DATASET UPLOAD
|
|
31
|
+
# -----------------------------
|
|
32
|
+
def upload_dataset(workspace, project_name, dataset_path):
|
|
33
|
+
|
|
34
|
+
print("[blue]🚀 Initializing Roboflow SDK...[/blue]")
|
|
35
|
+
|
|
36
|
+
rf = Roboflow(api_key=ROBOFLOW_API_KEY)
|
|
37
|
+
project = rf.workspace(workspace).project(project_name)
|
|
38
|
+
|
|
39
|
+
if not os.path.exists(dataset_path):
|
|
40
|
+
print("[red]❌ Invalid dataset path[/red]")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# 🔥 CASE 1: SPLIT DATASET
|
|
44
|
+
splits = ["train", "valid", "test"]
|
|
45
|
+
split_exists = any(os.path.exists(os.path.join(dataset_path, s)) for s in splits)
|
|
46
|
+
|
|
47
|
+
if split_exists:
|
|
48
|
+
print("[cyan]📂 Detected SPLIT dataset[/cyan]")
|
|
49
|
+
_handle_split_dataset(project, dataset_path)
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# 🔥 CASE 2: COCO JSON in root
|
|
53
|
+
json_files = [f for f in os.listdir(dataset_path) if f.endswith(".json")]
|
|
54
|
+
|
|
55
|
+
if json_files:
|
|
56
|
+
print("[cyan]📦 Detected COCO dataset[/cyan]")
|
|
57
|
+
|
|
58
|
+
json_path = os.path.join(dataset_path, json_files[0])
|
|
59
|
+
coco_to_yolo(json_path, dataset_path)
|
|
60
|
+
|
|
61
|
+
# 🔥 CASE 3: YOLO FLAT DATASET
|
|
62
|
+
print("[cyan]📁 Detected YOLO flat dataset[/cyan]")
|
|
63
|
+
_handle_flat_dataset(project, dataset_path)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# -----------------------------
|
|
67
|
+
# 📂 HANDLE SPLIT DATASET
|
|
68
|
+
# -----------------------------
|
|
69
|
+
def _handle_split_dataset(project, dataset_path):
|
|
70
|
+
|
|
71
|
+
splits = ["train", "valid", "test"]
|
|
72
|
+
|
|
73
|
+
success, fail = 0, 0
|
|
74
|
+
|
|
75
|
+
for split in splits:
|
|
76
|
+
|
|
77
|
+
split_path = os.path.join(dataset_path, split)
|
|
78
|
+
|
|
79
|
+
if not os.path.exists(split_path):
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
print(f"\n[bold cyan]Processing {split}...[/bold cyan]")
|
|
83
|
+
|
|
84
|
+
# Convert COCO if exists
|
|
85
|
+
json_files = [f for f in os.listdir(split_path) if f.endswith(".json")]
|
|
86
|
+
if json_files:
|
|
87
|
+
coco_to_yolo(os.path.join(split_path, json_files[0]), split_path)
|
|
88
|
+
|
|
89
|
+
labels_dir = os.path.join(split_path, "labels")
|
|
90
|
+
|
|
91
|
+
images = [f for f in os.listdir(split_path) if f.lower().endswith((".jpg", ".jpeg", ".png"))]
|
|
92
|
+
|
|
93
|
+
for i, img_name in enumerate(images):
|
|
94
|
+
|
|
95
|
+
img_path = os.path.join(split_path, img_name)
|
|
96
|
+
label_path = os.path.join(labels_dir, os.path.splitext(img_name)[0] + ".txt")
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
if os.path.exists(label_path):
|
|
100
|
+
project.upload(img_path, annotation_path=label_path, split=split)
|
|
101
|
+
else:
|
|
102
|
+
project.upload(img_path, split=split)
|
|
103
|
+
|
|
104
|
+
success += 1
|
|
105
|
+
|
|
106
|
+
if success % 10 == 0:
|
|
107
|
+
print(f"[green]{success} uploaded...[/green]")
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
print(f"[red]Failed {img_name}: {e}[/red]")
|
|
111
|
+
fail += 1
|
|
112
|
+
|
|
113
|
+
print(f"\n[bold]Done → Success: {success}, Failed: {fail}[/bold]")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# -----------------------------
|
|
117
|
+
# 📁 HANDLE FLAT DATASET
|
|
118
|
+
# -----------------------------
|
|
119
|
+
def _handle_flat_dataset(project, dataset_path):
|
|
120
|
+
|
|
121
|
+
images = [f for f in os.listdir(dataset_path) if f.lower().endswith((".jpg", ".jpeg", ".png"))]
|
|
122
|
+
|
|
123
|
+
labels_dir = os.path.join(dataset_path, "labels")
|
|
124
|
+
|
|
125
|
+
success, fail = 0, 0
|
|
126
|
+
|
|
127
|
+
for i, img_name in enumerate(images):
|
|
128
|
+
|
|
129
|
+
img_path = os.path.join(dataset_path, img_name)
|
|
130
|
+
label_path = os.path.join(labels_dir, os.path.splitext(img_name)[0] + ".txt")
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
if os.path.exists(label_path):
|
|
134
|
+
project.upload(img_path, annotation_path=label_path, split="train")
|
|
135
|
+
else:
|
|
136
|
+
project.upload(img_path)
|
|
137
|
+
|
|
138
|
+
success += 1
|
|
139
|
+
|
|
140
|
+
if success % 100 == 0:
|
|
141
|
+
print(f"[green]{success} uploaded...[/green]")
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
print(f"[red]Failed {img_name}: {e}[/red]")
|
|
145
|
+
fail += 1
|
|
146
|
+
|
|
147
|
+
print(f"\n[bold]Done → Success: {success}, Failed: {fail}[/bold]")
|
|
File without changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from rich import print
|
|
2
|
+
from roboflow import Roboflow
|
|
3
|
+
from rfcli.config import ROBOFLOW_API_KEY
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_version(workspace, project_name, version_name):
|
|
7
|
+
|
|
8
|
+
print("[blue]🚀 Creating dataset version with split...[/blue]")
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
rf = Roboflow(api_key=ROBOFLOW_API_KEY)
|
|
12
|
+
project = rf.workspace(workspace).project(project_name)
|
|
13
|
+
|
|
14
|
+
# 🔥 Create version with splits
|
|
15
|
+
version = project.generate_version(
|
|
16
|
+
{
|
|
17
|
+
"name": version_name,
|
|
18
|
+
|
|
19
|
+
# ✅ SPLIT CONFIG
|
|
20
|
+
"splits": {
|
|
21
|
+
"train": 70,
|
|
22
|
+
"valid": 20,
|
|
23
|
+
"test": 10
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
# ✅ PREPROCESSING
|
|
27
|
+
"preprocessing": {
|
|
28
|
+
"resize": {
|
|
29
|
+
"width": 640,
|
|
30
|
+
"height": 640,
|
|
31
|
+
"format": "Stretch to"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
# (optional)
|
|
36
|
+
"augmentation": {}
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
print("[green]✅ Version created successfully![/green]")
|
|
41
|
+
print(f"[bold]Version Name:[/bold] {version_name}")
|
|
42
|
+
|
|
43
|
+
print("\n[cyan]📊 Version Info:[/cyan]")
|
|
44
|
+
print(version)
|
|
45
|
+
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"[red]❌ Failed to create version: {str(e)}[/red]")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rfcli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dataset-to-Deployment CLI for Object Detection
|
|
5
|
+
Author: Your Name
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Requires-Dist: click
|
|
8
|
+
Requires-Dist: rich
|
|
9
|
+
Requires-Dist: roboflow
|
|
10
|
+
Requires-Dist: ultralytics
|
|
11
|
+
Requires-Dist: fastapi
|
|
12
|
+
Requires-Dist: uvicorn
|
|
13
|
+
Requires-Dist: python-multipart
|
|
14
|
+
Requires-Dist: opencv-python
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: requires-dist
|
|
17
|
+
Dynamic: requires-python
|
|
18
|
+
Dynamic: summary
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
rfcli/__init__.py
|
|
4
|
+
rfcli/config.py
|
|
5
|
+
rfcli/convert.py
|
|
6
|
+
rfcli/infer.py
|
|
7
|
+
rfcli/main.py
|
|
8
|
+
rfcli/predict.py
|
|
9
|
+
rfcli/train.py
|
|
10
|
+
rfcli/train_local.py
|
|
11
|
+
rfcli/upload.py
|
|
12
|
+
rfcli/utils.py
|
|
13
|
+
rfcli/version.py
|
|
14
|
+
rfcli.egg-info/PKG-INFO
|
|
15
|
+
rfcli.egg-info/SOURCES.txt
|
|
16
|
+
rfcli.egg-info/dependency_links.txt
|
|
17
|
+
rfcli.egg-info/entry_points.txt
|
|
18
|
+
rfcli.egg-info/requires.txt
|
|
19
|
+
rfcli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rfcli
|
rfcli-0.1.0/setup.cfg
ADDED
rfcli-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="rfcli",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
packages=find_packages(),
|
|
7
|
+
install_requires=[
|
|
8
|
+
"click",
|
|
9
|
+
"rich",
|
|
10
|
+
"roboflow",
|
|
11
|
+
"ultralytics",
|
|
12
|
+
"fastapi",
|
|
13
|
+
"uvicorn",
|
|
14
|
+
"python-multipart",
|
|
15
|
+
"opencv-python"
|
|
16
|
+
],
|
|
17
|
+
entry_points={
|
|
18
|
+
"console_scripts": [
|
|
19
|
+
"rfcli=rfcli.main:cli"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
author="Your Name",
|
|
23
|
+
description="Dataset-to-Deployment CLI for Object Detection",
|
|
24
|
+
python_requires=">=3.8",
|
|
25
|
+
)
|