Skip to content

Commit 0414d5f

Browse files
committed
Merge branch 'develop' of https://github.com/TissueImageAnalytics/tiatoolbox into address-review-comments
2 parents cdc2896 + d5c1995 commit 0414d5f

39 files changed

+1516
-165
lines changed

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
sudo apt update
3131
sudo apt-get install -y libopenjp2-7 libopenjp2-tools
3232
python -m pip install --upgrade pip
33-
python -m pip install ruff==0.11.13 pytest pytest-cov pytest-runner
33+
python -m pip install ruff==0.12.7 pytest pytest-cov pytest-runner
3434
pip install -r requirements/requirements.txt
3535
- name: Cache tiatoolbox static assets
3636
uses: actions/cache@v3

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ repos:
6060
- id: rst-inline-touching-normal # Detect mistake of inline code touching normal text in rst.
6161
- repo: https://github.com/astral-sh/ruff-pre-commit
6262
# Ruff version.
63-
rev: v0.11.13
63+
rev: v0.12.7
6464
hooks:
6565
- id: ruff
6666
args: [--fix, --exit-non-zero-on-fix]

benchmarks/annotation_store_alloc.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ def __exit__(self: memray, *args: object) -> None:
141141

142142
import numpy as np
143143
import psutil
144+
from shapely import affinity
144145
from shapely.geometry import Polygon
145146
from tqdm import tqdm
146147

@@ -188,8 +189,6 @@ def cell_polygon(
188189
round_coords (bool): Round coordinates to integers. Defaults to False.
189190
190191
"""
191-
from shapely import affinity
192-
193192
rand_state = np.random.default_rng().__getstate__()
194193
rng = np.random.default_rng(seed)
195194
if repeat_first:

docs/images/dual_win_reg.png

3.23 MB
Loading

docs/visualization.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,55 @@ A filter can be applied to annotations using the filter box. For example, enteri
119119

120120
The main slide view can be made fullscreen by clicking the fullscreen icon in the small toolbar to the immediate right of the main window. This toolbar also provides a button to save the current view as a .png file.
121121

122+
Visualising Image Registration/Transformation
123+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
124+
125+
.. image:: images/dual_win_reg.png
126+
:width: 100%
127+
:align: center
128+
:alt: dual window example
129+
130+
131+
TIAToolbox provides a powerful registration visualization feature that enables intuitive alignment and comparison of histopathology images—such as H&E and IHC-stained slides—without requiring full whole-slide registration. This is particularly useful for quickly inspecting the accuracy of precomputed registration results.
132+
133+
To use this feature, you must supply a precomputed registration matrix (e.g., .mha or .npy file) generated from an affine or deformable registration process. This matrix is used to align the images visually.
134+
135+
Dual Window Mode:
136+
"""""""""""""""""
137+
138+
This mode allows side-by-side comparison of registered images.
139+
140+
**Steps:**
141+
142+
* Open **Dual Window Mode** and load the images.
143+
144+
* In one window, open the H&E (source) image.
145+
146+
* In the other window, open the IHC (target) image.
147+
148+
* Load the registration file (e.g., an .mha or .npy file) as an overlay on the source image.
149+
150+
Overlay Mode:
151+
"""""""""""""
152+
153+
This mode overlays the registered image directly on top of the source image for visual inspection.
154+
155+
156+
**Steps:**
157+
158+
* Open the H&E (source) image.
159+
160+
* Overlay the IHC (target) image on the source image.
161+
162+
* Load the registration file (e.g., an .mha or .npy file) as an overlay on the source image.
163+
164+
.. note::
165+
Always load the **target image first** when using overlays. If not, the system may incorrectly assume both images are the same, leading to inaccurate transformations. Incorrect ordering may result in misaligned overlays or misleading visualizations.
166+
167+
168+
The **order** of source and target images must remain consistent with how the registration matrix was computed. This is as most registration algorithms require the dimensions of both the source and target images to perform the registration transformation. The above examples assume that the H&E image is registered to the IHC images, but if instead you have registered the IHC to the H&E image then please change the order of image loading accordingly.
169+
170+
122171
.. _data_format:
123172

124173
3. Data Format Conventions and File Structure

pre-commit/notebook_urls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
def git_branch_name() -> str:
1515
"""Get the current branch name."""
1616
return (
17-
subprocess.check_output( # noqa: S603
17+
subprocess.check_output(
1818
["/usr/bin/git", "rev-parse", "--abbrev-ref", "HEAD"],
1919
)
2020
.decode()
@@ -45,7 +45,7 @@ def git_previous_commit_modified_paths() -> set[Path]:
4545
"""Get a set of file paths modified in the previous commit."""
4646
return {
4747
Path(p)
48-
for p in subprocess.check_output( # noqa: S603
48+
for p in subprocess.check_output(
4949
["/usr/bin/git", "diff", "--name-only", "HEAD~"],
5050
)
5151
.decode()

pre-commit/requirements_consistency.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def parse_setup_py(file_path: Path) -> dict[str, Requirement]:
115115
pkg_resources.Requirement.
116116
"""
117117
mock_setup = {}
118-
import setuptools
118+
import setuptools # noqa: PLC0415
119119

120120
setuptools.setup = lambda **kw: mock_setup.update(kw)
121121
spec = importlib.util.spec_from_file_location("setup", str(file_path))

requirements/requirements_dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pytest>=7.2.0
1010
pytest-cov>=4.0.0
1111
pytest-runner>=6.0
1212
pytest-xdist[psutil]
13-
ruff==0.11.13 # This will be updated by pre-commit bot to latest version
13+
ruff==0.12.7 # This will be updated by pre-commit bot to latest version
1414
toml>=0.10.2
1515
twine>=4.0.1
1616
wheel>=0.37.1

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -589,9 +589,9 @@ def chdir() -> Callable:
589589
590590
"""
591591
try:
592-
from contextlib import chdir
592+
from contextlib import chdir # noqa: PLC0415
593593
except ImportError:
594-
from contextlib import AbstractContextManager
594+
from contextlib import AbstractContextManager # noqa: PLC0415
595595

596596
class chdir(AbstractContextManager): # noqa: N801
597597
"""Non thread-safe context manager to change the current working directory.

tests/test_app_bokeh.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@
1111
from pathlib import Path
1212
from typing import TYPE_CHECKING
1313

14-
import bokeh.models as bkmodels
1514
import matplotlib.pyplot as plt
1615
import numpy as np
1716
import pytest
1817
import requests
19-
from bokeh.application import Application
20-
from bokeh.application.handlers import FunctionHandler
21-
from bokeh.events import ButtonClick, DoubleTap, MenuItemClick
2218
from flask_cors import CORS
2319
from matplotlib import colormaps
2420
from PIL import Image
2521
from scipy.ndimage import label
2622

23+
import bokeh.models as bkmodels
24+
from bokeh.application import Application
25+
from bokeh.application.handlers import FunctionHandler
26+
from bokeh.events import ButtonClick, DoubleTap, MenuItemClick
2727
from tiatoolbox.data import _fetch_remote_sample
2828
from tiatoolbox.visualization.bokeh_app import main
2929
from tiatoolbox.visualization.tileserver import TileServer
@@ -125,6 +125,11 @@ def annotation_path(data_path: dict[str, Path]) -> dict[str, object]:
125125
"annotation_dat_svs_1",
126126
data_path["base_path"] / "overlays",
127127
)
128+
data_path["affine_trans"] = (
129+
data_path["base_path"] / "overlays" / (data_path["slide1"].stem + ".npy")
130+
)
131+
# save eye as test identity transform
132+
np.save(data_path["affine_trans"], np.eye(3))
128133
data_path["config"] = _fetch_remote_sample(
129134
"config_2",
130135
data_path["base_path"] / "overlays",
@@ -246,6 +251,31 @@ def test_remove_dual_window(doc: Document, data_path: pytest.TempPathFactory) ->
246251
assert main.UI["vstate"].slide_path == data_path["slide1"]
247252

248253

254+
def test_add_slide_layer(doc: Document, data_path: pytest.TempPathFactory) -> None:
255+
"""Test adding a non-annotation slide layer."""
256+
slide_select = doc.get_model_by_name("slide_select0")
257+
slide_select.value = [data_path["slide1"].name]
258+
259+
layer_drop = doc.get_model_by_name("layer_drop0")
260+
slide_layer_path = str(data_path["slide1"])
261+
262+
click = MenuItemClick(layer_drop, slide_layer_path)
263+
layer_drop._trigger_event(click)
264+
265+
assert len(layer_drop.menu) == 6
266+
267+
268+
def test_transform_overlay(doc: Document, data_path: pytest.TempPathFactory) -> None:
269+
"""Test adding a transform overlay."""
270+
layer_drop = doc.get_model_by_name("layer_drop0")
271+
affine_layer_path = str(data_path["affine_trans"]) # sample .npy file
272+
273+
click = MenuItemClick(layer_drop, affine_layer_path)
274+
layer_drop._trigger_event(click)
275+
276+
assert len(layer_drop.menu) == 6
277+
278+
249279
def test_add_annotation_layer(doc: Document, data_path: pytest.TempPathFactory) -> None:
250280
"""Test adding annotation layers."""
251281
# test loading a geojson file.
@@ -263,7 +293,7 @@ def test_add_annotation_layer(doc: Document, data_path: pytest.TempPathFactory)
263293
# test loading an annotation store
264294
slide_select.value = [data_path["slide1"].name]
265295
layer_drop = doc.get_model_by_name("layer_drop0")
266-
assert len(layer_drop.menu) == 5
296+
assert len(layer_drop.menu) == 6
267297
n_renderers = len(doc.get_model_by_name("slide_windows").children[0].renderers)
268298
# trigger an event to select the annotation .db file
269299
click = MenuItemClick(layer_drop, str(data_path["annotations"]))

0 commit comments

Comments
 (0)