Skip to content

Commit d4da76d

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 9c1d2ce commit d4da76d

21 files changed

Lines changed: 116 additions & 146 deletions

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: 13 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
from sahi.scripts.coco_error_analysis import analyse
88
from sahi.scripts.coco_evaluation import evaluate
99
from sahi.scripts.slice_coco import slice
10-
from sahi.utils.import_utils import print_environment_info
10+
from sahi.utils.cli_helper import make_click_command
11+
from sahi.utils.package_utils import print_environment_info
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

19+
1720
coco_app = {
1821
"evaluate": evaluate,
1922
"analyse": analyse,
@@ -31,90 +34,23 @@ def cli():
3134
"env": print_environment_info,
3235
}
3336

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-
10237

10338
def app() -> None:
10439
"""Cli app."""
105-
#fire.Fire(sahi_app)
106-
# for loop to add commands to cli
40+
10741
for command_name, command_func in sahi_app.items():
10842
if isinstance(command_func, dict):
109-
# add subcommands
110-
sub_cli = click.Group(command_name)
43+
# add subcommands (create a named Group with help text)
44+
sub_cli = click.Group(command_name, help=f"{command_name} related commands")
11145
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))
46+
sub_cli.add_command(make_click_command(sub_command_name, sub_command_func))
11347
cli.add_command(sub_cli)
11448
else:
115-
cli.add_command(_make_click_command(command_name, command_func))
49+
cli.add_command(make_click_command(command_name, command_func))
11650

11751
cli()
11852

53+
11954
if __name__ == "__main__":
120-
cli()
55+
# build the application (register commands) and run
56+
app()

sahi/models/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from sahi.logger import logger
66
from sahi.prediction import ObjectPrediction
7-
from sahi.utils.import_utils import check_requirements
7+
from sahi.utils.package_utils import check_requirements
88
from sahi.utils.torch_utils import empty_cuda_cache, select_device
99

1010

sahi/models/huggingface.py

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

1212

1313
class HuggingfaceDetectionModel(DetectionModel):

sahi/models/mmdet.py

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

1212
check_requirements(["torch", "mmdet", "mmcv", "mmengine"]) # noqa: E402
1313

sahi/models/ultralytics.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_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

1515
class UltralyticsDetectionModel(DetectionModel):

sahi/models/yolov5.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from sahi.models.base import DetectionModel
77
from sahi.prediction import ObjectPrediction
88
from sahi.utils.compatibility import fix_full_shape_list, fix_shift_amount_list
9-
from sahi.utils.import_utils import check_package_minimum_version, check_requirements
9+
from sahi.utils.package_utils import check_package_minimum_version, check_requirements
1010

1111

1212
class Yolov5DetectionModel(DetectionModel):

sahi/postprocess/combine.py

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

1010

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

sahi/predict.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from PIL import Image
66

77
from sahi.logger import logger
8-
from sahi.utils.import_utils import is_available
8+
from sahi.utils.package_utils import is_available
99

1010
# TODO: This does nothing for this module. The issue named here does not exist
1111
# https://github.com/obss/sahi/issues/526
@@ -39,7 +39,7 @@
3939
visualize_object_predictions,
4040
)
4141
from sahi.utils.file import Path, increment_path, list_files, save_json, save_pickle
42-
from sahi.utils.import_utils import check_requirements
42+
from sahi.utils.package_utils import check_requirements
4343

4444
POSTPROCESS_NAME_TO_CLASS = {
4545
"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)