Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0442d7f
:white_check_mark: Optimise `test_app_bokeh`
shaneahmed Aug 20, 2026
3073f4f
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Aug 28, 2026
56724a7
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Sep 10, 2026
246c96d
:white_check_mark: Add test for `get_wsi_output_shape`
shaneahmed Sep 10, 2026
2f89c0d
:white_check_mark: Add test for `_update_run_params`
shaneahmed Sep 10, 2026
4a53f92
:white_check_mark: Add test for `process_contours`
shaneahmed Sep 10, 2026
090c41e
:bug: Fix deepsource errors
shaneahmed Sep 10, 2026
bc8925c
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Sep 10, 2026
7e841cd
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Sep 10, 2026
2f3aae7
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Sep 11, 2026
cc09c56
Merge remote-tracking branch 'origin/optimise-test-app-bokeh' into op…
shaneahmed Sep 11, 2026
5dcdd6e
:white_check_mark: Add test for `PromptSegmentor`
shaneahmed Sep 11, 2026
f50e039
:white_check_mark: Add test for `sam`
shaneahmed Sep 11, 2026
f29cc36
:bug: Fix deepsource errors
shaneahmed Sep 11, 2026
1a29b6d
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Sep 11, 2026
1700dfc
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Sep 23, 2026
451b095
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Sep 28, 2026
3064910
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Oct 2, 2026
73e6f4d
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Oct 9, 2026
cb1f7db
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Oct 16, 2026
96d240a
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Oct 30, 2026
7c240e5
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Oct 30, 2026
8f6439e
:white_check_mark: Add tests to `test_ioconfig`
shaneahmed Oct 30, 2026
c582906
:white_check_mark: Add tests to `test_arch_sam`
shaneahmed Oct 30, 2026
87082b7
Merge branch 'develop' into optimise-test-app-bokeh
shaneahmed Oct 30, 2026
6fb0dee
:pencil2: Fix typo
shaneahmed Oct 30, 2026
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
92 changes: 92 additions & 0 deletions tests/engines/test_engine_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pathlib import Path
from typing import TYPE_CHECKING, Unpack

import dask
import dask.array as da
import numpy as np
import pytest
Expand Down Expand Up @@ -543,6 +544,97 @@ def test_io_config_delegation(
)


def test_update_run_params_sets_dask_num_workers(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test Dask configuration when num_workers is greater than zero."""
eng = TestEngineABC(model="alexnet-kather100k")

captured: dict[str, object] = {}

def _fake_dask_config_set(**kwargs: object) -> None:
"""Capture Dask configuration."""
captured.update(kwargs)

def _fake_validate_input_numbers(
*,
images: object,
masks: object,
labels: object,
) -> None:
"""Fake validate input numbers."""
_ = images, masks, labels

def _fake_validate_images_masks(
*,
images: object,
) -> list:
"""Fake validate images masks."""
_ = images
return []

def _fake_load_ioconfig(
*,
ioconfig: object,
) -> object:
"""Fake load ioconfig."""
return ioconfig

def _fake_update_ioconfig(
ioconfig: object,
patch_input_shape: object,
stride_shape: object,
input_resolutions: object,
) -> object:
"""Fake update ioconfig."""
_ = patch_input_shape, stride_shape, input_resolutions
return ioconfig

monkeypatch.setattr(
dask.config,
"set",
_fake_dask_config_set,
)

monkeypatch.setattr(
eng,
"_validate_input_numbers",
_fake_validate_input_numbers,
)

monkeypatch.setattr(
eng,
"_validate_images_masks",
_fake_validate_images_masks,
)

monkeypatch.setattr(
eng,
"_load_ioconfig",
_fake_load_ioconfig,
)

monkeypatch.setattr(
eng,
"_update_ioconfig",
_fake_update_ioconfig,
)

eng.num_workers = 4
eng.return_labels = True
eng.drop_keys = []

eng._update_run_params(
images=[],
patch_mode=True,
output_type="dict",
save_dir=None,
)

assert captured["scheduler"] == "threads"
assert captured["num_workers"] == 4


def test_save_predictions_incorrect_output_type() -> None:
"""Engine should raise TypeError if incorrect output type is requested."""
eng = TestEngineABC(model="alexnet-kather100k")
Expand Down
60 changes: 59 additions & 1 deletion tests/engines/test_ioconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import numpy as np
import pytest

from tiatoolbox.models import IOSegmentorConfig, ModelIOConfigABC
from tiatoolbox.models import (
IOInstanceSegmentorConfig,
IOSegmentorConfig,
ModelIOConfigABC,
)


def test_validation_error_io_config() -> None:
Expand Down Expand Up @@ -119,3 +123,57 @@ def test_ios_to_baseline_without_save_resolution() -> None:

# save_resolution should remain None after conversion
assert new_cfg.save_resolution is None


def test_model_ioconfig_to_baseline_with_save_resolution() -> None:
"""Test baseline conversion when save_resolution is present."""
config = IOSegmentorConfig(
input_resolutions=[{"units": "mpp", "resolution": 0.25}],
output_resolutions=[{"units": "mpp", "resolution": 0.5}],
patch_input_shape=(256, 256),
patch_output_shape=(128, 128),
stride_shape=(128, 128),
save_resolution={"units": "mpp", "resolution": 1.0},
)

config.to_baseline()

# Coverage test, no assertion required beyond successful execution.
assert config.save_resolution is not None


def test_iosegmentorconfig_to_baseline_with_save_resolution() -> None:
"""Test IOSegmentorConfig baseline conversion with save resolution."""
config = IOSegmentorConfig(
input_resolutions=[{"units": "mpp", "resolution": 0.25}],
output_resolutions=[{"units": "mpp", "resolution": 0.5}],
patch_input_shape=(256, 256),
patch_output_shape=(128, 128),
stride_shape=(128, 128),
save_resolution={"units": "mpp", "resolution": 1.0},
)

baseline = config.to_baseline()

assert baseline.save_resolution is not None
assert baseline.save_resolution["units"] == "baseline"


def test_ioinstancesegmentorconfig_to_baseline() -> None:
"""Test IOInstanceSegmentorConfig baseline conversion."""
config = IOInstanceSegmentorConfig(
input_resolutions=[{"units": "mpp", "resolution": 0.25}],
output_resolutions=[{"units": "mpp", "resolution": 0.5}],
patch_input_shape=(256, 256),
patch_output_shape=(128, 128),
stride_shape=(128, 128),
margin=32,
tile_shape=(512, 512),
)

result = config.to_baseline()

assert isinstance(
result,
IOInstanceSegmentorConfig,
)
197 changes: 197 additions & 0 deletions tests/engines/test_prompt_segmentor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
"""Test PromptSegmentor."""

from pathlib import Path

import numpy as np
import pytest

from tiatoolbox.models.engine.prompt_segmentor import PromptSegmentor


def test_prompt_segmentor_calc_mpp_no_scaling() -> None:
"""Test calc_mpp when no scaling is required."""
segmentor = PromptSegmentor(model=object())

mpp, scale = segmentor.calc_mpp(
area_dims=(500, 1000),
base_mpp=0.5,
fixed_size=1500,
)

assert mpp == 0.5
assert scale == 1.0
assert segmentor.scale == 1.0


def test_prompt_segmentor_calc_mpp_with_scaling() -> None:
"""Test calc_mpp when scaling is required."""
segmentor = PromptSegmentor(model=object())

mpp, scale = segmentor.calc_mpp(
area_dims=(3000, 2000),
base_mpp=0.5,
fixed_size=1500,
)

assert scale == 2.0
assert mpp == 1.0
assert segmentor.scale == 2.0


def test_prompt_segmentor_run(
monkeypatch: pytest.MonkeyPatch,
track_tmp_path: Path,
) -> None:
"""Test prompt segmentor inference workflow."""
called: dict[str, object] = {}

class FakeSAM:
"""Minimal stand-in for SAM."""

@staticmethod
def infer_batch(
model: object,
images: list,
point_coords: np.ndarray | None = None,
box_coords: np.ndarray | None = None,
*,
device: str = "cpu",
) -> tuple[np.ndarray, np.ndarray]:
"""Fake inference logic."""
_ = (
model,
images,
point_coords,
box_coords,
device,
)

masks = np.array(
[
[
[
[True, False],
[False, True],
],
],
],
dtype=bool,
)

scores = np.array([1.0])

return masks, scores

def _fake_dict_to_store_semantic_segmentor(
patch_output: dict,
scale_factor: tuple[float, float],
offset: np.ndarray,
save_path: Path,
output_type: str,
ignore_index: int,
) -> None:
"""Capture annotation-store calls."""
called["patch_output"] = patch_output
called["scale_factor"] = scale_factor
called["offset"] = offset
called["save_path"] = save_path
called["output_type"] = output_type
called["ignore_index"] = ignore_index

monkeypatch.setattr(
"tiatoolbox.models.engine.prompt_segmentor.dict_to_store_semantic_segmentor",
_fake_dict_to_store_semantic_segmentor,
)

segmentor = PromptSegmentor(model=FakeSAM())

result = segmentor.run(
images=[np.zeros((10, 10, 3), dtype=np.uint8)],
point_coords=np.array([[[1, 1]]]),
save_dir=track_tmp_path,
)

expected_path = track_tmp_path / "0.db"

assert result == [expected_path]

assert called["save_path"] == expected_path
assert called["output_type"] == "annotationstore"
assert called["ignore_index"] == 0


def test_prompt_segmentor_run_multiple_masks(
monkeypatch: pytest.MonkeyPatch,
track_tmp_path: Path,
) -> None:
"""Test processing multiple returned masks."""
save_paths: list[Path] = []

class FakeSAM:
"""Minimal stand-in for SAM."""

@staticmethod
def infer_batch(
model: object,
images: list,
point_coords: np.ndarray | None = None,
box_coords: np.ndarray | None = None,
*,
device: str = "cpu",
) -> tuple[np.ndarray, np.ndarray]:
"""Fake inference logic."""
_ = (
model,
images,
point_coords,
box_coords,
device,
)

masks = np.zeros(
(2, 1, 1, 4, 4),
dtype=bool,
)

scores = np.zeros((2,))

return masks, scores

def _fake_dict_to_store_semantic_segmentor(
patch_output: dict,
scale_factor: tuple[float, float],
offset: np.ndarray,
save_path: Path,
output_type: str,
ignore_index: int,
) -> None:
"""Fake dictionary to store semantic segmentor."""
_ = (
patch_output,
scale_factor,
offset,
output_type,
ignore_index,
)

save_paths.append(save_path)

monkeypatch.setattr(
"tiatoolbox.models.engine.prompt_segmentor.dict_to_store_semantic_segmentor",
_fake_dict_to_store_semantic_segmentor,
)

segmentor = PromptSegmentor(model=FakeSAM())

result = segmentor.run(
images=[np.zeros((10, 10, 3), dtype=np.uint8)],
point_coords=np.array([[[1, 1]]]),
save_dir=track_tmp_path,
)

assert result == [
track_tmp_path / "0.db",
track_tmp_path / "1.db",
]

assert len(save_paths) == 2
Loading
Loading