Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 1 addition & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,50 +145,7 @@ In the NN Archive configuration, there are two flags related to color encoding c

## Online Usage

The preferred way of using ModelConverter is in the online mode, where the conversion is performed on a remote server.

For more detailed documentation on the online conversion, please refer to the documentation available [here](modelconverter/hub/README.md).

To start with the online conversion, you need to create an account on the [HubAI](https://hub.luxonis.com) platform and obtain the API key for your team.

To log in to HubAI, use the following command:

```bash
modelconverter hub login
```

> [!NOTE]
> The key can also be stored in an environment variable `HUBAI_API_KEY`. In such a case, it takes precedence over the saved key.

**CLI Example:**

```bash
modelconverter hub convert rvc4 --path configs/resnet18.yaml
```

**CLI YOLO Example:**

```bash
modelconverter hub convert rvc4 --path yolov6nr4.pt --name "YOLOv6R4" --yolo-input-shape "480 480" --yolo-version "yolov6r4" --yolo-class-names "person, rabbit, cactus"
```

**Python Example:**

```python
from modelconverter import convert

# if your API key is not stored in the environment variable or .env file
from modelconverter.utils import environ

environ.HUBAI_API_KEY = "your_api_key"

converted_model = convert.RVC4("configs/resnet18.yaml")
```

We have prepared several examples for you to check and are actively working on providing more. You can find them [here](https://github.com/luxonis/ai-tutorials/tree/main/conversion).

> [!NOTE]
> To learn more about the available options, use `modelconverter hub convert --help`.
You can run model conversion directly in the cloud using our [HubAI SDK](https://github.com/luxonis/hubai-sdk), either with Python or using the CLI.

## Local Usage

Expand Down
4 changes: 0 additions & 4 deletions modelconverter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@

from luxonis_ml.utils import PUT_FILE_REGISTRY

from .hub import convert

__version__ = "0.4.5"

__all__ = ["convert"]


def load_put_file_plugins() -> None:
"""Registers any external put file plugins."""
Expand Down
2 changes: 0 additions & 2 deletions modelconverter/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
get_output_dir_name,
init_dirs,
)
from modelconverter.hub.__main__ import app as hub_app
from modelconverter.packages import (
get_analyzer,
get_benchmark,
Expand Down Expand Up @@ -48,7 +47,6 @@
name="Modelconverter",
version=lambda: f"ModelConverter v{importlib.metadata.version('modelconv')}",
)
app.meta.command(hub_app, name="hub")

app.meta.group_parameters = Group("Global Parameters", sort_key=0)
app["--help"].group = app.meta.group_parameters
Expand Down
263 changes: 2 additions & 261 deletions modelconverter/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from rich.progress import Progress
from rich.table import Table

from modelconverter.hub.hub_requests import Request
from modelconverter.utils.hub_requests import Request
from modelconverter.utils import (
process_nn_archive,
resolve_path,
Expand Down Expand Up @@ -170,143 +170,6 @@ def extract_preprocessing(
return cfg, preprocessing


def print_hub_resource_info(
model: dict[str, Any],
keys: list[str],
json: bool,
rename: dict[str, str] | None = None,
**kwargs,
) -> None:
rename = rename or {}

if json:
print(model)
return

console = Console()

if model.get("description_short"):
description_short_panel = Panel(
f"[italic]{model['description_short']}[/italic]",
border_style="dim",
box=ROUNDED,
)
else:
description_short_panel = None

table = Table(show_header=False, box=None)
table.add_column(justify="right", style="bold")
table.add_column()
for key in keys:
if key in ["created", "updated", "last_version_added"]:
value = model.get(key, "N/A")

def format_date(date_str: str) -> RenderableType:
try:
date_obj = datetime.strptime(
date_str, "%Y-%m-%dT%H:%M:%S.%f"
)
return date_obj.strftime("%B %d, %Y %H:%M:%S")
except (ValueError, TypeError):
return Pretty("N/A")

formatted_value = format_date(value)

table.add_row(
f"{rename.get(key, key).replace('_', ' ').title()}:",
formatted_value,
)
elif key == "is_public":
if key not in model:
value = "N/A"
else:
value = "Public" if model[key] else "Private"
table.add_row("Visibility", Pretty(value))
elif key == "is_commercial":
# TODO: Is Usage the right term?
if key not in model:
value = "N/A"
else:
value = "Commercial" if model[key] else "Non-Commercial"
table.add_row("Usage:", Pretty(value))
elif key == "is_nn_archive":
table.add_row("NN Archive:", Pretty(model.get(key, False)))
else:
table.add_row(
f"{rename.get(key, key).replace('_', ' ').title()}:",
Pretty(model.get(key, "N/A")),
)

info_panel = Panel(table, border_style="cyan", box=ROUNDED, **kwargs)

if model.get("description"):
description_panel = Panel(
Markdown(model["description"]),
title="Description",
border_style="green",
box=ROUNDED,
)
else:
description_panel = None

nested_panels = []
if description_short_panel:
nested_panels.append(description_short_panel)
nested_panels.append(info_panel)
if description_panel:
nested_panels.append(description_panel)

content = Group(*nested_panels)

main_panel = Panel(
content,
title=f"[bold magenta]{model.get('name', 'N/A')}[/bold magenta]",
width=74,
border_style="magenta",
box=ROUNDED,
)

console.print(main_panel)


def hub_ls(
endpoint: str,
keys: list[str],
rename: dict[str, str] | None = None,
*,
_silent: bool = False,
**kwargs,
) -> list[dict[str, Any]]:
rename = rename or {}
data = Request.get(f"{endpoint}/", params=kwargs)
table = Table(row_styles=["yellow", "cyan"], box=ROUNDED)
for key in keys:
table.add_column(rename.get(key, key), header_style="magenta i")

for model in data:
renderables = []
for key in keys:
value = str(model.get(key, "N/A"))
if isinstance(value, list):
value = ", ".join(value)
renderables.append(value)
table.add_row(*renderables)

if not _silent:
console = Console()
console.print(table)
return data


def is_valid_uuid(uuid_string: str) -> bool:
try:
UUID(uuid_string)
except Exception:
return False
else:
return True


def slug_to_id(
slug: str, endpoint: Literal["models", "modelVersions", "modelInstances"]
) -> str:
Expand All @@ -319,126 +182,4 @@ def slug_to_id(
data = Request.get(f"{endpoint}/", params=params)
if data:
return data[0]["id"]
raise ValueError(f"Model with slug '{slug}' not found.")


def get_resource_id(
identifier: str,
endpoint: Literal["models", "modelVersions", "modelInstances"],
) -> str:
if is_valid_uuid(identifier):
return identifier
return slug_to_id(identifier, endpoint)


def request_info(
identifier: str,
endpoint: Literal["models", "modelVersions", "modelInstances"],
) -> dict[str, Any]:
resource_id = get_resource_id(identifier, endpoint)

try:
return Request.get(f"{endpoint}/{resource_id}/")
except HTTPError:
typer.echo(f"Resource with ID '{resource_id}' not found.")
sys.exit(1)


def get_variant_name(
cfg: SingleStageConfig, model_type: ModelType, name: str
) -> str:
shape = cfg.inputs[0].shape
layout = cfg.inputs[0].layout

if shape is not None:
if layout is not None and "H" in layout and "W" in layout:
h, w = shape[layout.index("H")], shape[layout.index("W")]
return f"{name} {h}x{w}"
if len(shape) == 4:
if model_type == ModelType.TFLITE:
h, w = shape[1], shape[2]
else:
h, w = shape[2], shape[3]
return f"{name} {h}x{w}"
return name


def get_version_number(model_id: str) -> str:
versions = Request.get("modelVersions/", params={"model_id": model_id})
if not versions:
return "0.1.0"
max_version = Version(versions[0]["version"])
for v in versions[1:]:
max_version = max(max_version, Version(v["version"]))
max_version = str(max_version)
version_numbers = max_version.split(".")
version_numbers[-1] = str(int(version_numbers[-1]) + 1)
return ".".join(version_numbers)


def wait_for_export(run_id: str) -> None:
def _get_run(run_id: str) -> dict[str, Any]:
return Request.dag_get(f"runs/{run_id}")

def _clean_logs(logs: str) -> str:
pattern = r"\[.*?\] \{.*?\} INFO - \[base\] logs:\s*"
return re.sub(pattern, "", logs)

with Progress() as progress:
progress.add_task("Waiting for the conversion to finish", total=None)
run = _get_run(run_id)
while run["status"] in ["PENDING", "RUNNING"]:
sleep(10)
run = _get_run(run_id)

if run["status"] == "FAILURE":
if run["logs"] is None:
print(run["id"])
raise RuntimeError("Export failed with no logs.")

if run["logs"] is None:
raise RuntimeError("Export failed with no logs.")

while len(run["logs"].split("\n")) < 5:
run = _get_run(run_id)
sleep(5)

logs = _clean_logs(run["logs"])
raise RuntimeError(f"Export failed with\n{logs}.")


def get_target_specific_options(
target: Target, cfg: SingleStageConfig, tool_version: str | None = None
) -> dict[str, Any]:
json_cfg = cfg.model_dump(mode="json")
options = {
"disable_onnx_simplification": cfg.disable_onnx_simplification,
"disable_onnx_optimization": cfg.disable_onnx_optimization,
"inputs": json_cfg["inputs"],
}
if target is Target.RVC4:
options["snpe_onnx_to_dlc_args"] = cfg.rvc4.snpe_onnx_to_dlc_args
options["snpe_dlc_quant_args"] = cfg.rvc4.snpe_dlc_quant_args
options["snpe_dlc_graph_prepare_args"] = (
cfg.rvc4.snpe_dlc_graph_prepare_args
)
if tool_version is not None:
options["snpe_version"] = tool_version
elif target in [Target.RVC2, Target.RVC3]:
target_cfg = getattr(cfg, target.value)
options["mo_args"] = target_cfg.mo_args
options["compile_tool_args"] = target_cfg.compile_tool_args
if tool_version is not None:
options["ir_version"] = tool_version
if target is Target.RVC3:
options["pot_target_device"] = cfg.rvc3.pot_target_device
if target is Target.RVC2:
options["superblob"] = cfg.rvc2.superblob
options["number_of_shaves"] = cfg.rvc2.number_of_shaves
elif target is Target.HAILO:
options["optimization_level"] = cfg.hailo.optimization_level
options["compression_level"] = cfg.hailo.compression_level
options["batch_size"] = cfg.hailo.batch_size
options["disable_calibration"] = cfg.hailo.disable_calibration

return options
raise ValueError(f"Model with slug '{slug}' not found.")
Loading
Loading