Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ The CLI is a thin wrapper over the package API.
Bundled configs live under `slide2vec/configs/preprocessing/` and `slide2vec/configs/models/`.

```shell
python -m slide2vec --config-file /path/to/config.yaml
slide2vec /path/to/config.yaml
```

By default, manifest-driven CLI runs use all available GPUs. Set `speed.num_gpus=4` when you want to cap the sharding explicitly.
Expand Down
17 changes: 8 additions & 9 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The CLI is usually the better fit for:
## Basic Command

```shell
python -m slide2vec --config-file /path/to/config.yaml
slide2vec /path/to/config.yaml
```

This command:
Expand Down Expand Up @@ -68,8 +68,7 @@ In practice, the config controls:
You can override config values from the command line with `path.key=value` syntax:

```shell
python -m slide2vec \
--config-file /path/to/config.yaml \
slide2vec /path/to/config.yaml \
output_dir=/tmp/slide2vec-run \
speed.num_gpus=4 \
model.name=virchow2
Expand Down Expand Up @@ -112,7 +111,7 @@ tiling:
Or override from the command line:

```shell
python -m slide2vec --config-file /path/to/config.yaml tiling.gpu_decode=true
slide2vec /path/to/config.yaml tiling.gpu_decode=true
```

When enabled, two things happen:
Expand All @@ -134,7 +133,7 @@ By default, the CLI uses all available GPUs.
To cap GPU usage, set:

```shell
python -m slide2vec --config-file /path/to/config.yaml speed.num_gpus=4
slide2vec /path/to/config.yaml speed.num_gpus=4
```

If you pass `--run-on-cpu`, the CLI uses CPU execution instead.
Expand Down Expand Up @@ -170,23 +169,23 @@ When stdout is not interactive, the CLI falls back to plain text stage updates a
Full batch run:

```shell
python -m slide2vec --config-file /path/to/config.yaml
slide2vec /path/to/config.yaml
```

Full batch run with limited GPU count:

```shell
python -m slide2vec --config-file /path/to/config.yaml speed.num_gpus=2
slide2vec /path/to/config.yaml speed.num_gpus=2
```

Tiling only:

```shell
python -m slide2vec --config-file /path/to/config.yaml --tiling-only
slide2vec /path/to/config.yaml --tiling-only
```

CPU run:

```shell
python -m slide2vec --config-file /path/to/config.yaml --run-on-cpu
slide2vec /path/to/config.yaml --run-on-cpu
```
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Homepage = "https://github.com/clemsgrs/slide2vec"
"Bug Tracker" = "https://github.com/clemsgrs/slide2vec/issues"

[project.scripts]
slide2vec = "slide2vec.cli:main"
slide2vec = "slide2vec.cli:entrypoint"

[project.optional-dependencies]
hoptimus = [
Expand Down
4 changes: 2 additions & 2 deletions scripts/generate_gt.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ def main():
print("Running pipeline...")
subprocess.run(
[
sys.executable, "-m", "slide2vec",
"--config-file", str(cfg_path),
"slide2vec",
str(cfg_path),
"--skip-datetime",
"--run-on-cpu",
],
Expand Down
21 changes: 12 additions & 9 deletions slide2vec/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,21 @@

def get_args_parser(add_help: bool = True):
parser = argparse.ArgumentParser("slide2vec", add_help=add_help)
parser.add_argument("--config-file", default="", metavar="FILE", help="path to config file")
parser.add_argument("config_file", metavar="CONFIG", help="path to config file")
parser.add_argument("--skip-datetime", action="store_true", help="skip run id datetime prefix")
parser.add_argument("--tiling-only", action="store_true", help="only run slide tiling")
parser.add_argument("--run-on-cpu", action="store_true", help="run inference on cpu")
parser.add_argument("--output-dir", type=str, default=None, help="output directory to save artifacts")
parser.add_argument(
"opts",
help='Modify config options at the end of the command using "path.key=value".',
default=None,
nargs=argparse.REMAINDER,
)
return parser


def parse_args(argv=None):
parser = get_args_parser(add_help=True)
args, opts = parser.parse_known_args(argv)
args.opts = opts
return args


def build_model_and_pipeline(args):
cfg, _cfg_path = setup(args)
hf_login()
Expand All @@ -39,8 +40,7 @@ def build_model_and_pipeline(args):


def main(argv=None):
parser = get_args_parser(add_help=True)
args = parser.parse_args(argv)
args = parse_args(argv)
pipeline, cfg = build_model_and_pipeline(args)
reporter = progress.create_cli_progress_reporter(output_dir=getattr(cfg, "output_dir", None))
with progress.activate_progress_reporter(reporter):
Expand All @@ -50,3 +50,6 @@ def main(argv=None):
)


def entrypoint(argv=None):
main(argv)
return 0
4 changes: 4 additions & 0 deletions tasks/lessons.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Lessons Learned

## 2026-04-12

- When refactoring CLI parsing to support `parse_known_args()`, prefer updating the test double to match the real parser API instead of adding a production fallback for mocks. Keep the runtime code clean unless the fallback is genuinely needed by real callers.

## 2026-04-10

- In this environment, never route `apply_patch` through `exec_command`; use the dedicated `apply_patch` tool directly for file edits.
Expand Down
4 changes: 2 additions & 2 deletions tests/test_output_consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ def test_output_consistency(wsi_path, mask_path, tmp_path):
# 3. Run the pipeline
subprocess.run(
[
sys.executable, "-m", "slide2vec",
"--config-file", str(cfg_path),
"slide2vec",
str(cfg_path),
"--skip-datetime",
"--run-on-cpu",
],
Expand Down
36 changes: 36 additions & 0 deletions tests/test_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ class FakeParser:
def parse_args(self, argv=None):
return SimpleNamespace(tiling_only=False)

def parse_known_args(self, argv=None):
return self.parse_args(argv), []

monkeypatch.setattr(cli, "get_args_parser", lambda add_help=True: FakeParser())
monkeypatch.setattr(
cli,
Expand All @@ -128,6 +131,39 @@ def parse_args(self, argv=None):
assert isinstance(progress.get_progress_reporter(), progress.NullProgressReporter)


def test_cli_entrypoint_returns_zero(monkeypatch):
import slide2vec.cli as cli

observed = {}

def fake_main(argv=None):
observed["argv"] = argv
return "ok"

monkeypatch.setattr(cli, "main", fake_main)

assert cli.entrypoint(["/tmp/config.yaml"]) == 0
assert observed["argv"] == ["/tmp/config.yaml"]


def test_cli_parse_args_preserves_flags_and_config_overrides():
import slide2vec.cli as cli

args = cli.parse_args(
[
"/tmp/config.yaml",
"--skip-datetime",
"--run-on-cpu",
"speed.num_gpus=4",
]
)

assert args.config_file == "/tmp/config.yaml"
assert args.skip_datetime is True
assert args.run_on_cpu is True
assert args.opts == ["speed.num_gpus=4"]


def test_run_pipeline_emits_local_progress_events_in_order(monkeypatch, tmp_path: Path):
import slide2vec.inference as inference
import slide2vec.progress as progress
Expand Down
Loading