Skip to content

Commit 123f37e

Browse files
committed
refactor: 🛠️ update import paths from import_utils to package_utils and enhance CLI command structure
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
1 parent 0172236 commit 123f37e

22 files changed

+209
-145
lines changed

docs/cli.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
# CLI Commands
1+
## CLI commands
2+
3+
This page documents the SAHI CLI (the program exposed by `sahi/cli.py`).
4+
5+
### Top-level commands
6+
7+
- `predict` — run sliced/standard prediction pipeline and export results (images, COCO json, pickles)
8+
- `predict-fiftyone` — run prediction and open results in FiftyOne
9+
- `coco` — subgroup for COCO-format utilities (evaluate, analyse, convert, slice)
10+
- `version` — print SAHI package version
11+
- `env` — print environment and dependency versions
212

313
## `predict` command usage
414

sahi/cli.py

Lines changed: 12 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
from sahi.scripts.coco_error_analysis import analyse
88
from sahi.scripts.coco_evaluation import evaluate
99
from sahi.scripts.slice_coco import slicer
10-
from sahi.utils.import_utils import print_environment_info
10+
from sahi.utils.package_utils import print_environment_info
11+
from sahi.utils.cli_helper import make_click_command
1112

1213

13-
@click.group()
14+
@click.group(help="SAHI command-line utilities: slicing-aided high-resolution inference and COCO tools")
1415
def cli():
16+
"""Top-level click group for SAHI CLI."""
1517
pass
1618

1719
coco_app = {
@@ -31,90 +33,23 @@ def cli():
3133
"env": print_environment_info,
3234
}
3335

34-
def _make_callback(obj):
35-
"""Return a callable suitable for click.Command:
36-
- if obj is callable, call it with whatever click passes;
37-
- otherwise print the object (e.g. version string).
38-
"""
39-
if callable(obj):
40-
def _cb(*args, **kwargs):
41-
return obj(*args, **kwargs)
42-
else:
43-
def _cb(*args, **kwargs):
44-
click.echo(str(obj))
45-
return _cb
46-
47-
import inspect
48-
49-
50-
def _click_params_from_signature(func):
51-
"""Create a list of click.Parameter (Argument/Option) objects from a Python
52-
callable's signature. This provides a lightweight automatic mapping so
53-
CLI options are available without manually writing decorators.
54-
55-
Rules (simple, pragmatic):
56-
- positional parameters without default -> click.Argument (required)
57-
- parameters with a default -> click.Option named --param-name
58-
- bool defaults -> is_flag option
59-
- list/tuple defaults -> multiple option
60-
- skip *args/**kwargs and (self, cls)
61-
- use annotation or default value to infer type when possible
62-
"""
63-
params = []
64-
sig = inspect.signature(func)
65-
for name, p in sig.parameters.items():
66-
# skip common unrepresentable params
67-
if name in ("self", "cls"):
68-
continue
69-
if p.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
70-
# skip *args/**kwargs
71-
continue
72-
73-
if p.default is inspect._empty:
74-
# required positional argument
75-
params.append(click.Argument([name]))
76-
else:
77-
opt_name = f"--{name}"
78-
# boolean flags
79-
if isinstance(p.default, bool):
80-
params.append(click.Option([opt_name], is_flag=True, default=p.default, help=f"(auto) default={p.default}"))
81-
# lists/tuples -> multiple
82-
elif isinstance(p.default, (list, tuple)):
83-
params.append(click.Option([opt_name], multiple=True, default=tuple(p.default), help="(auto) multiple"))
84-
else:
85-
# infer type from annotation or default value
86-
param_type = None
87-
if p.annotation is not inspect._empty and p.annotation in (int, float, str, bool):
88-
param_type = p.annotation
89-
elif p.default is not None:
90-
param_type = type(p.default)
91-
else:
92-
param_type = str
93-
params.append(click.Option([opt_name], default=p.default, type=param_type, help=f"(auto) default={p.default}"))
94-
return params
95-
96-
97-
def _make_click_command(name, func):
98-
"""Build a click.Command for `func`, auto-generating params from signature if callable."""
99-
params = _click_params_from_signature(func) if callable(func) else []
100-
return click.Command(name, params=params, callback=_make_callback(func))
101-
10236

10337
def app() -> None:
10438
"""Cli app."""
105-
#fire.Fire(sahi_app)
106-
# for loop to add commands to cli
39+
10740
for command_name, command_func in sahi_app.items():
10841
if isinstance(command_func, dict):
109-
# add subcommands
110-
sub_cli = click.Group(command_name)
42+
# add subcommands (create a named Group with help text)
43+
sub_cli = click.Group(command_name, help=f"{command_name} related commands")
11144
for sub_command_name, sub_command_func in command_func.items():
112-
sub_cli.add_command(_make_click_command(sub_command_name, sub_command_func))
45+
sub_cli.add_command(make_click_command(sub_command_name, sub_command_func))
11346
cli.add_command(sub_cli)
11447
else:
115-
cli.add_command(_make_click_command(command_name, command_func))
48+
cli.add_command(make_click_command(command_name, command_func))
11649

11750
cli()
11851

52+
11953
if __name__ == "__main__":
120-
cli()
54+
# build the application (register commands) and run
55+
app()

sahi/models/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from sahi.annotation import Category
88
from sahi.logger import logger
99
from sahi.prediction import ObjectPrediction
10-
from sahi.utils.import_utils import check_requirements
10+
from sahi.utils.package_utils import check_requirements
1111
from sahi.utils.torch_utils import empty_cuda_cache, select_device
1212

1313

sahi/models/huggingface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from sahi.models.base import DetectionModel
1010
from sahi.prediction import ObjectPrediction
1111
from sahi.utils.compatibility import fix_full_shape_list, fix_shift_amount_list
12-
from sahi.utils.import_utils import ensure_package_minimum_version
12+
from sahi.utils.package_utils import ensure_package_minimum_version
1313

1414

1515
class HuggingfaceDetectionModel(DetectionModel):

sahi/models/mmdet.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from sahi.prediction import ObjectPrediction
1010
from sahi.utils.compatibility import fix_full_shape_list, fix_shift_amount_list
1111
from sahi.utils.cv import get_bbox_from_bool_mask, get_coco_segmentation_from_bool_mask
12-
from sahi.utils.import_utils import check_requirements
12+
from sahi.utils.package_utils import check_requirements
1313

1414
check_requirements(["torch", "mmdet", "mmcv", "mmengine"])
1515

sahi/models/ultralytics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from sahi.prediction import ObjectPrediction
1111
from sahi.utils.compatibility import fix_full_shape_list, fix_shift_amount_list
1212
from sahi.utils.cv import get_coco_segmentation_from_bool_mask
13-
from sahi.utils.import_utils import check_requirements
13+
from sahi.utils.package_utils import check_requirements
1414

1515

1616
class UltralyticsDetectionModel(DetectionModel):

sahi/models/yolov5.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from sahi.models.base import DetectionModel
99
from sahi.prediction import ObjectPrediction
1010
from sahi.utils.compatibility import fix_full_shape_list, fix_shift_amount_list
11-
from sahi.utils.import_utils import check_package_minimum_version
11+
from sahi.utils.package_utils import check_package_minimum_version
1212

1313

1414
class Yolov5DetectionModel(DetectionModel):

sahi/postprocess/combine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from sahi.logger import logger
88
from sahi.postprocess.utils import ObjectPredictionList, has_match, merge_object_prediction_pair
99
from sahi.prediction import ObjectPrediction
10-
from sahi.utils.import_utils import check_requirements
10+
from sahi.utils.package_utils import check_requirements
1111

1212

1313
def batched_nms(predictions: torch.tensor, match_metric: str = "IOU", match_threshold: float = 0.5):

sahi/predict.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
33
import os
44
import time
55
from collections.abc import Generator
6+
7+
from PIL import Image
8+
9+
from sahi.logger import logger
10+
from sahi.utils.package_utils import is_available
11+
12+
# TODO: This does nothing for this module. The issue named here does not exist
13+
# https://github.com/obss/sahi/issues/526
14+
if is_available("torch"):
15+
import torch # noqa: F401
16+
617
from functools import cmp_to_key
718

819
import numpy as np
@@ -32,7 +43,7 @@
3243
visualize_object_predictions,
3344
)
3445
from sahi.utils.file import Path, increment_path, list_files, save_json, save_pickle
35-
from sahi.utils.import_utils import check_requirements
46+
from sahi.utils.package_utils import check_requirements
3647

3748
POSTPROCESS_NAME_TO_CLASS = {
3849
"GREEDYNMM": GreedyNMMPostprocess,

sahi/scripts/coco2fiftyone.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import time
22
from pathlib import Path
33

4-
import click
5-
64
from sahi.utils.file import load_json
75

86

@@ -75,7 +73,3 @@ def main(
7573
print(f"SAHI has successfully launched a Fiftyone app at http://localhost:{fo.config.default_app_port}")
7674
while 1:
7775
time.sleep(3)
78-
79-
80-
if __name__ == "__main__":
81-
fire.Fire(main)

0 commit comments

Comments
 (0)