Skip to content
This repository was archived by the owner on Mar 21, 2024. It is now read-only.

Commit a6b1516

Browse files
authored
Fix for stuck Linux build: Move pytest to Windows (#652)
Also renamed many build legs so that they can be found more easily in the UI.
1 parent b8fe0eb commit a6b1516

File tree

15 files changed

+167
-72
lines changed

15 files changed

+167
-72
lines changed

.github/workflows/check_changelog.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ name: Check Changelog
66
on:
77
pull_request:
88
jobs:
9-
check:
9+
check_changelog:
10+
name: Check Changelog
1011
runs-on: ubuntu-latest
1112
if: ${{ contains(github.event.pull_request.labels.*.name, 'no changelog needed') == 0 }}
1213
steps:

.github/workflows/codeql-analysis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ on:
1010
- cron: '45 4 * * 1'
1111

1212
jobs:
13-
analyze:
14-
name: Analyze
13+
codeql_analyze:
14+
name: CodeQL Analyze
1515
runs-on: ubuntu-latest
1616

1717
strategy:

.github/workflows/issues_to_ado.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ on:
66
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
77

88
jobs:
9-
alert:
9+
issues_to_ado:
10+
name: Sync issues with Azure DevOps
1011
runs-on: ubuntu-latest
1112
steps:
1213
- uses: danhellem/github-actions-issue-to-work-item@master

.github/workflows/linting_and_hello_world.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ on:
77
pull_request:
88

99
jobs:
10-
linux:
10+
flake_mypy_helloworld_linux:
11+
name: Flake8, MyPy, HelloWorld on Linux
1112
runs-on: ubuntu-latest
1213
steps:
1314
- uses: actions/checkout@v2
@@ -53,7 +54,8 @@ jobs:
5354
PYTHONPATH: ${{ github.workspace }}
5455
if: always()
5556

56-
windows:
57+
hello_world_windows:
58+
name: HelloWorld on Windows
5759
runs-on: windows-latest
5860
steps:
5961
- uses: actions/checkout@v2

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ in inference-only runs when using lightning containers.
119119
- ([#628](https://github.com/microsoft/InnerEye-DeepLearning/pull/628)) SSL SimCLR using the wrong LR schedule when running on multiple nodes
120120
- ([#638](https://github.com/microsoft/InnerEye-DeepLearning/pull/638)) SimClr cosine LR scheduler was using wrong length information when using with long linear head datasets
121121
- ([#612](https://github.com/microsoft/InnerEye-DeepLearning/pull/612)) SSL online evaluator was not doing distributed training
122+
- ([#652](https://github.com/microsoft/InnerEye-DeepLearning/pull/652)) Run pytest build on Windows after Linux agent version upgrade
122123

123124
### Removed
124125

InnerEye/ML/Histopathology/datasets/panda_dataset.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@
22
# Copyright (c) Microsoft Corporation. All rights reserved.
33
# Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
44
# ------------------------------------------------------------------------------------------
5-
5+
import logging
66
from pathlib import Path
77
from typing import Any, Dict, Union, Optional
88

99
import pandas as pd
10-
from cucim import CuImage
1110
from health_ml.utils import box_utils
1211
from monai.config import KeysCollection
1312
from monai.data.image_reader import ImageReader, WSIReader
1413
from monai.transforms import MapTransform
1514

1615
from InnerEye.ML.Histopathology.datasets.base_dataset import SlidesDataset
1716

17+
try:
18+
from cucim import CuImage
19+
except:
20+
logging.warning("cucim library not available, code may fail.")
21+
1822

1923
class PandaDataset(SlidesDataset):
2024
"""Dataset class for loading files from the PANDA challenge dataset.
@@ -48,6 +52,7 @@ def __init__(self,
4852
# MONAI's convention is that dictionary transforms have a 'd' suffix in the class name
4953
class ReadImaged(MapTransform):
5054
"""Basic transform to read image files."""
55+
5156
def __init__(self, reader: ImageReader, keys: KeysCollection,
5257
allow_missing_keys: bool = False, **kwargs: Any) -> None:
5358
super().__init__(keys, allow_missing_keys=allow_missing_keys)
@@ -71,6 +76,7 @@ class LoadPandaROId(MapTransform):
7176
- `'level'` (int): chosen magnification level
7277
- `'scale'` (float): corresponding scale, loaded from the file
7378
"""
79+
7480
def __init__(self, reader: WSIReader, image_key: str = 'image', mask_key: str = 'mask',
7581
level: int = 0, margin: int = 0, **kwargs: Any) -> None:
7682
"""
@@ -88,7 +94,7 @@ def __init__(self, reader: WSIReader, image_key: str = 'image', mask_key: str =
8894
self.margin = margin
8995
self.kwargs = kwargs
9096

91-
def _get_bounding_box(self, mask_obj: CuImage) -> box_utils.Box:
97+
def _get_bounding_box(self, mask_obj: 'CuImage') -> box_utils.Box:
9298
# Estimate bounding box at the lowest resolution (i.e. highest level)
9399
highest_level = mask_obj.resolutions['level_count'] - 1
94100
scale = mask_obj.resolutions['level_downsamples'][highest_level]

InnerEye/ML/Histopathology/preprocessing/loading.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1+
import logging
12
from typing import Dict, Optional, Tuple
23

34
import numpy as np
45
import skimage.filters
5-
from cucim import CuImage
66
from health_ml.utils import box_utils
77
from monai.data.image_reader import WSIReader
88
from monai.transforms import MapTransform
99

1010
from InnerEye.ML.Histopathology.utils.naming import SlideKey
1111

12+
try:
13+
from cucim import CuImage
14+
except:
15+
logging.warning("cucim library not available, code may fail.")
16+
1217

1318
def get_luminance(slide: np.ndarray) -> np.ndarray:
1419
"""Compute a grayscale version of the input slide.
@@ -35,7 +40,7 @@ def segment_foreground(slide: np.ndarray, threshold: Optional[float] = None) \
3540
return luminance < threshold, threshold
3641

3742

38-
def load_slide_at_level(reader: WSIReader, slide_obj: CuImage, level: int) -> np.ndarray:
43+
def load_slide_at_level(reader: WSIReader, slide_obj: 'CuImage', level: int) -> np.ndarray:
3944
"""Load full slide array at the given magnification level.
4045
4146
This is a manual workaround for a MONAI bug (https://github.com/Project-MONAI/MONAI/issues/3415)
@@ -60,6 +65,7 @@ class LoadROId(MapTransform):
6065
- `SlideKey.SCALE` (float): corresponding scale, loaded from the file
6166
- `SlideKey.FOREGROUND_THRESHOLD` (float): threshold used to segment the foreground
6267
"""
68+
6369
def __init__(self, reader: WSIReader, image_key: str = SlideKey.IMAGE, level: int = 0,
6470
margin: int = 0, foreground_threshold: Optional[float] = None) -> None:
6571
"""
@@ -77,7 +83,7 @@ def __init__(self, reader: WSIReader, image_key: str = SlideKey.IMAGE, level: in
7783
self.margin = margin
7884
self.foreground_threshold = foreground_threshold
7985

80-
def _get_bounding_box(self, slide_obj: CuImage) -> Tuple[box_utils.Box, float]:
86+
def _get_bounding_box(self, slide_obj: 'CuImage') -> Tuple[box_utils.Box, float]:
8187
# Estimate bounding box at the lowest resolution (i.e. highest level)
8288
highest_level = slide_obj.resolutions['level_count'] - 1
8389
scale = slide_obj.resolutions['level_downsamples'][highest_level]
@@ -88,6 +94,7 @@ def _get_bounding_box(self, slide_obj: CuImage) -> Tuple[box_utils.Box, float]:
8894
return bbox, threshold
8995

9096
def __call__(self, data: Dict) -> Dict:
97+
from cucim import CuImage
9198
image_obj: CuImage = self.reader.read(data[self.image_key])
9299

93100
level0_bbox, threshold = self._get_bounding_box(image_obj)

Tests/Azure/test_azure_config.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from InnerEye.Azure.azure_config import AzureConfig
1111
from InnerEye.Azure.azure_runner import create_dataset_configs
12+
from InnerEye.Common.common_util import is_linux
1213
from InnerEye.ML.deep_learning_config import DatasetParams
1314
from Tests.ML.util import get_default_azure_config
1415

@@ -65,8 +66,10 @@ def test_dataset_consumption2() -> None:
6566
assert datasets[1].name == "2"
6667
assert datasets[0].local_folder == Path("l1")
6768
assert datasets[1].local_folder == Path("l2")
68-
assert datasets[0].target_folder == PosixPath("mp1")
69-
assert datasets[1].target_folder == PosixPath("mp2")
69+
if is_linux():
70+
# PosixPath cannot be instantiated on Windows
71+
assert datasets[0].target_folder == PosixPath("mp1")
72+
assert datasets[1].target_folder == PosixPath("mp2")
7073

7174

7275
def test_dataset_consumption3() -> None:

Tests/ML/histopathology/models/test_deepmil.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Callable, Dict, List, Type # noqa
88

99
import pytest
10+
import torch
1011
from torch import Tensor, argmax, nn, rand, randint, randn, round, stack, allclose
1112
from torchvision.models import resnet18
1213

@@ -29,7 +30,7 @@
2930
)
3031
from InnerEye.ML.Histopathology.models.deepmil import DeepMILModule
3132
from InnerEye.ML.Histopathology.models.encoders import ImageNetEncoder, TileEncoder
32-
from InnerEye.ML.Histopathology.utils.naming import ResultsKey
33+
from InnerEye.ML.Histopathology.utils.naming import MetricsKey, ResultsKey
3334

3435

3536
def get_supervised_imagenet_encoder() -> TileEncoder:
@@ -38,10 +39,10 @@ def get_supervised_imagenet_encoder() -> TileEncoder:
3839

3940
@pytest.mark.parametrize("n_classes", [1, 3])
4041
@pytest.mark.parametrize("pooling_layer", [AttentionLayer, GatedAttentionLayer])
41-
@pytest.mark.parametrize("batch_size", [1, 15])
42-
@pytest.mark.parametrize("max_bag_size", [1, 7])
43-
@pytest.mark.parametrize("pool_hidden_dim", [1, 5])
44-
@pytest.mark.parametrize("pool_out_dim", [1, 6])
42+
@pytest.mark.parametrize("batch_size", [1, 2])
43+
@pytest.mark.parametrize("max_bag_size", [1, 3])
44+
@pytest.mark.parametrize("pool_hidden_dim", [1, 4])
45+
@pytest.mark.parametrize("pool_out_dim", [1, 5])
4546
def test_lightningmodule(
4647
n_classes: int,
4748
pooling_layer: Callable[[int, int, int], nn.Module],
@@ -108,9 +109,12 @@ def test_lightningmodule(
108109
assert preds.shape[0] == batch_size
109110

110111
for metric_name, metric_object in module.train_metrics.items():
111-
if (batch_size > 1) or (not metric_name == "auroc"):
112+
if metric_name == MetricsKey.CONF_MATRIX or metric_name == MetricsKey.AUROC:
113+
continue
114+
if batch_size > 1:
112115
score = metric_object(preds.view(-1, 1), bag_labels.view(-1, 1))
113-
assert score >= 0 and score <= 1
116+
assert torch.all(score >= 0)
117+
assert torch.all(score <= 1)
114118

115119

116120
def move_batch_to_expected_device(batch: Dict[str, List], use_gpu: bool) -> Dict:

Tests/ML/histopathology/preprocessing/test_slide_loading.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,24 @@
22

33
import numpy as np
44
import pytest
5-
from cucim import CuImage
65
from monai.data.image_reader import WSIReader
76

7+
from InnerEye.Common.common_util import is_windows
88
from InnerEye.Common.fixed_paths_for_tests import tests_root_directory
99
from InnerEye.ML.Histopathology.preprocessing.tiling import tile_array_2d
10-
from InnerEye.ML.Histopathology.preprocessing.loading import LoadROId, get_luminance, load_slide_at_level, segment_foreground
10+
from InnerEye.ML.Histopathology.preprocessing.loading import (LoadROId, get_luminance, load_slide_at_level,
11+
segment_foreground)
1112
from InnerEye.ML.Histopathology.utils.naming import SlideKey
1213
from Tests.ML.histopathology.datasets.test_slides_dataset import MockSlidesDataset
1314

1415
TEST_IMAGE_PATH = str(tests_root_directory("ML/histopathology/test_data/panda_wsi_example.tiff"))
1516

1617

18+
@pytest.mark.skipif(is_windows(), reason="cucim package is not available on Windows")
1719
def test_load_slide() -> None:
1820
level = 2
1921
reader = WSIReader('cuCIM')
22+
from cucim import CuImage
2023
slide_obj: CuImage = reader.read(TEST_IMAGE_PATH)
2124
dims = slide_obj.resolutions['level_dimensions'][level][::-1]
2225

@@ -39,9 +42,11 @@ def test_load_slide() -> None:
3942
assert np.array_equiv(larger_slide[:, :, dims[1]:], empty_fill_value)
4043

4144

45+
@pytest.mark.skipif(is_windows(), reason="cucim package is not available on Windows")
4246
def test_get_luminance() -> None:
4347
level = 2 # here we only need to test at a single resolution
4448
reader = WSIReader('cuCIM')
49+
from cucim import CuImage
4550
slide_obj: CuImage = reader.read(TEST_IMAGE_PATH)
4651

4752
slide = load_slide_at_level(reader, slide_obj, level)
@@ -61,9 +66,11 @@ def test_get_luminance() -> None:
6166
assert np.array_equal(slide_luminance_tiles.squeeze(1), tiles_luminance)
6267

6368

69+
@pytest.mark.skipif(is_windows(), reason="cucim package is not available on Windows")
6470
def test_segment_foreground() -> None:
6571
level = 2 # here we only need to test at a single resolution
6672
reader = WSIReader('cuCIM')
73+
from cucim import CuImage
6774
slide_obj: CuImage = reader.read(TEST_IMAGE_PATH)
6875
slide = load_slide_at_level(reader, slide_obj, level)
6976

@@ -95,11 +102,13 @@ def test_segment_foreground() -> None:
95102

96103
@pytest.mark.parametrize('level', [1, 2])
97104
@pytest.mark.parametrize('foreground_threshold', [None, 215])
105+
@pytest.mark.skipif(is_windows(), reason="cucim package is not available on Windows")
98106
def test_get_bounding_box(level: int, foreground_threshold: Optional[float]) -> None:
99107
margin = 0
100108
reader = WSIReader('cuCIM')
101109
loader = LoadROId(reader, image_key=SlideKey.IMAGE, level=level, margin=margin,
102110
foreground_threshold=foreground_threshold)
111+
from cucim import CuImage
103112
slide_obj: CuImage = reader.read(TEST_IMAGE_PATH)
104113
level0_bbox, _ = loader._get_bounding_box(slide_obj)
105114

@@ -130,6 +139,7 @@ def test_get_bounding_box(level: int, foreground_threshold: Optional[float]) ->
130139
@pytest.mark.parametrize('level', [1, 2])
131140
@pytest.mark.parametrize('margin', [0, 42])
132141
@pytest.mark.parametrize('foreground_threshold', [None, 215])
142+
@pytest.mark.skipif(is_windows(), reason="cucim package is not available on Windows")
133143
def test_load_roi(level: int, margin: int, foreground_threshold: Optional[float]) -> None:
134144
dataset = MockSlidesDataset()
135145
sample = dataset[0]

0 commit comments

Comments
 (0)