Skip to content

Commit 24dfe19

Browse files
authored
Merge pull request #123 from ImageMarkup/score-error
2 parents ed6a76b + 6b58239 commit 24dfe19

8 files changed

Lines changed: 50 additions & 48 deletions

File tree

isic_challenge_scoring/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
from isic_challenge_scoring.classification import ClassificationMetric, ClassificationScore
44
from isic_challenge_scoring.segmentation import SegmentationScore
5-
from isic_challenge_scoring.types import ScoreException
5+
from isic_challenge_scoring.types import ScoreError
66

7-
__all__ = ['ClassificationScore', 'SegmentationScore', 'ScoreException', 'ClassificationMetric']
7+
ScoreException = ScoreError
8+
9+
__all__ = ['ClassificationScore', 'SegmentationScore', 'ScoreError', 'ClassificationMetric']
810

911
try:
1012
__version__ = version('isic-challenge-scoring')

isic_challenge_scoring/__main__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from isic_challenge_scoring.classification import ClassificationMetric, ClassificationScore
99
from isic_challenge_scoring.segmentation import SegmentationScore
10-
from isic_challenge_scoring.types import ScoreException
10+
from isic_challenge_scoring.types import ScoreError
1111

1212
DirectoryPath = click_pathlib.Path(exists=True, file_okay=False, dir_okay=True, readable=True)
1313
FilePath = click_pathlib.Path(exists=True, file_okay=True, dir_okay=False, readable=True)
@@ -26,7 +26,7 @@ def cli(output: str) -> None:
2626
def segmentation(ctx: click.Context, truth_dir: pathlib.Path, prediction_dir: pathlib.Path) -> None:
2727
try:
2828
score = SegmentationScore.from_dir(truth_dir, prediction_dir)
29-
except ScoreException as e:
29+
except ScoreError as e:
3030
raise click.ClickException(str(e))
3131

3232
output: str = cast(click.Context, ctx.parent).params['output']
@@ -53,7 +53,7 @@ def classification(
5353
score = ClassificationScore.from_file(
5454
truth_file, prediction_file, ClassificationMetric(metric)
5555
)
56-
except ScoreException as e:
56+
except ScoreError as e:
5757
raise click.ClickException(str(e))
5858

5959
output: str = cast(click.Context, ctx.parent).params['output']

isic_challenge_scoring/load_csv.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import numpy as np
44
import pandas as pd
55

6-
from isic_challenge_scoring.types import ScoreException
6+
from isic_challenge_scoring.types import ScoreError
77

88

99
def parse_truth_csv(csv_file_stream: TextIO) -> Tuple[pd.DataFrame, pd.DataFrame]:
@@ -31,19 +31,19 @@ def parse_csv(csv_file_stream: TextIO, categories: pd.Index) -> pd.DataFrame:
3131
if csv_file_stream.read(2000).count('\n') < 2:
3232
# Heuristic: if there aren't 2 newlines in the first 2000 characters, it's probably
3333
# invalid, and we don't want to hang or crash the parser
34-
raise ScoreException('No newlines detected in CSV.')
34+
raise ScoreError('No newlines detected in CSV.')
3535
csv_file_stream.seek(0)
3636

3737
try:
3838
probabilities = pd.read_csv(csv_file_stream, header=0, index_col=False)
3939
except (pd.errors.ParserError, pd.errors.EmptyDataError) as e:
4040
# TODO: Test something that generates a ParserError
41-
raise ScoreException(f'Could not parse CSV: "{str(e)}".')
41+
raise ScoreError(f'Could not parse CSV: "{str(e)}".')
4242
except UnicodeDecodeError:
43-
raise ScoreException('Could not parse CSV: could not decode file as UTF-8.')
43+
raise ScoreError('Could not parse CSV: could not decode file as UTF-8.')
4444

4545
if 'image' not in probabilities.columns:
46-
raise ScoreException("Missing column in CSV: 'image'.")
46+
raise ScoreError("Missing column in CSV: 'image'.")
4747

4848
# Pandas represents strings as 'O' (object)
4949
if probabilities['image'].dtype != np.dtype('O'):
@@ -57,31 +57,31 @@ def parse_csv(csv_file_stream: TextIO, categories: pd.Index) -> pd.DataFrame:
5757

5858
if not probabilities['image'].is_unique:
5959
duplicate_images = probabilities['image'][probabilities['image'].duplicated()].unique()
60-
raise ScoreException(f'Duplicate image rows detected in CSV: {duplicate_images.tolist()}.')
60+
raise ScoreError(f'Duplicate image rows detected in CSV: {duplicate_images.tolist()}.')
6161

6262
# The duplicate check is the same as performed by 'verify_integrity'
6363
probabilities.set_index('image', drop=True, inplace=True, verify_integrity=False)
6464

6565
missing_columns = categories.difference(probabilities.columns)
6666
if not missing_columns.empty:
67-
raise ScoreException(f'Missing columns in CSV: {missing_columns.tolist()}.')
67+
raise ScoreError(f'Missing columns in CSV: {missing_columns.tolist()}.')
6868

6969
extra_columns = probabilities.columns.difference(categories)
7070
if not extra_columns.empty:
71-
raise ScoreException(f'Extra columns in CSV: {extra_columns.tolist()}.')
71+
raise ScoreError(f'Extra columns in CSV: {extra_columns.tolist()}.')
7272

7373
# sort by the order in categories
7474
probabilities = probabilities.reindex(categories, axis='columns')
7575

7676
missing_rows = probabilities[probabilities.isnull().any(axis='columns')].index
7777
if not missing_rows.empty:
78-
raise ScoreException(f'Missing value(s) in CSV for images: {missing_rows.tolist()}.')
78+
raise ScoreError(f'Missing value(s) in CSV for images: {missing_rows.tolist()}.')
7979

8080
non_float_columns = probabilities.dtypes[
8181
probabilities.dtypes.apply(lambda x: x != np.float64)
8282
].index
8383
if not non_float_columns.empty:
84-
raise ScoreException(
84+
raise ScoreError(
8585
f'CSV contains non-floating-point value(s) in columns: {non_float_columns.tolist()}.'
8686
)
8787
# TODO: identify specific failed rows
@@ -90,7 +90,7 @@ def parse_csv(csv_file_stream: TextIO, categories: pd.Index) -> pd.DataFrame:
9090
probabilities.applymap(lambda x: x < 0.0 or x > 1.0).any(axis='columns')
9191
].index
9292
if not out_of_range_rows.empty:
93-
raise ScoreException(
93+
raise ScoreError(
9494
f'Values in CSV are outside the interval [0.0, 1.0] for images: '
9595
f'{out_of_range_rows.tolist()}.'
9696
)
@@ -111,11 +111,11 @@ def validate_rows(
111111
"""
112112
missing_images = truth_probabilities.index.difference(prediction_probabilities.index)
113113
if not missing_images.empty:
114-
raise ScoreException(f'Missing images in CSV: {missing_images.tolist()}.')
114+
raise ScoreError(f'Missing images in CSV: {missing_images.tolist()}.')
115115

116116
extra_images = prediction_probabilities.index.difference(truth_probabilities.index)
117117
if not extra_images.empty:
118-
raise ScoreException(f'Extra images in CSV: {extra_images.tolist()}.')
118+
raise ScoreError(f'Extra images in CSV: {extra_images.tolist()}.')
119119

120120

121121
def sort_rows(probabilities: pd.DataFrame) -> None:

isic_challenge_scoring/load_image.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from PIL import Image, UnidentifiedImageError
77
import numpy as np
88

9-
from isic_challenge_scoring.types import ScoreException
9+
from isic_challenge_scoring.types import ScoreError
1010

1111

1212
@dataclass
@@ -48,9 +48,9 @@ def find_prediction_file(self, prediction_path: pathlib.Path) -> None:
4848
]
4949

5050
if not prediction_file_candidates:
51-
raise ScoreException(f'No matching submission for: {self.truth_file.name}')
51+
raise ScoreError(f'No matching submission for: {self.truth_file.name}')
5252
elif len(prediction_file_candidates) > 1:
53-
raise ScoreException(f'Multiple matching submissions for: {self.truth_file.name}')
53+
raise ScoreError(f'Multiple matching submissions for: {self.truth_file.name}')
5454

5555
self.prediction_file = prediction_file_candidates[0]
5656

@@ -62,7 +62,7 @@ def load_truth_image(self) -> None:
6262
def load_prediction_image(self) -> None:
6363
self.prediction_image = load_segmentation_image(self.prediction_file)
6464
if self.prediction_image.shape[0:2] != self.truth_image.shape[0:2]:
65-
raise ScoreException(
65+
raise ScoreError(
6666
f'Image {self.prediction_file.name} has dimensions '
6767
f'{self.prediction_image.shape[0:2]}; expected {self.truth_image.shape[0:2]}.'
6868
)
@@ -81,12 +81,12 @@ def load_segmentation_image(image_path: pathlib.Path) -> np.ndarray:
8181
image = image.convert('L')
8282

8383
if image.mode != 'L':
84-
raise ScoreException(f'Image {image_path.name} is not single-channel (greyscale).')
84+
raise ScoreError(f'Image {image_path.name} is not single-channel (greyscale).')
8585

8686
np_image = np.asarray(image)
8787

8888
except UnidentifiedImageError:
89-
raise ScoreException(f'Could not decode image "{image_path.name}"')
89+
raise ScoreError(f'Could not decode image "{image_path.name}"')
9090

9191
return np_image
9292

@@ -103,9 +103,9 @@ def assert_binary_image(image: np.ndarray, image_path: pathlib.Path) -> np.ndarr
103103
image /= high_value
104104
image *= 255
105105
if set(np.unique(image)) > {0, 255}:
106-
raise ScoreException(f'Image {image_path.name} contains values other than 0 and 255.')
106+
raise ScoreError(f'Image {image_path.name} contains values other than 0 and 255.')
107107
else:
108-
raise ScoreException(f'Image {image_path.name} contains values other than 0 and 255.')
108+
raise ScoreError(f'Image {image_path.name} contains values other than 0 and 255.')
109109

110110
return image
111111

isic_challenge_scoring/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import Dict, List, Optional, Union
33

44

5-
class ScoreException(Exception): # noqa: N818
5+
class ScoreError(Exception):
66
pass
77

88

isic_challenge_scoring/unzip.py

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

77
import zipfile_deflate64 as zipfile
88

9-
from isic_challenge_scoring.types import ScoreException
9+
from isic_challenge_scoring.types import ScoreError
1010

1111

1212
def extract_zip(zip_path: pathlib.Path, output_path: pathlib.Path, flatten: bool = True) -> None:
@@ -34,7 +34,7 @@ def extract_zip(zip_path: pathlib.Path, output_path: pathlib.Path, flatten: bool
3434
else:
3535
zf.extractall(output_path)
3636
except zipfile.BadZipfile as e:
37-
raise ScoreException(f'Could not read ZIP file "{zip_path.name}": {str(e)}.')
37+
raise ScoreError(f'Could not read ZIP file "{zip_path.name}": {str(e)}.')
3838

3939

4040
def unzip_all(input_file: pathlib.Path) -> Tuple[pathlib.Path, tempfile.TemporaryDirectory]:

tests/test_load_csv.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55

66
from isic_challenge_scoring import load_csv
7-
from isic_challenge_scoring.types import ScoreException
7+
from isic_challenge_scoring.types import ScoreError
88

99

1010
def test_parse_truth_csv(categories):
@@ -97,7 +97,7 @@ def test_parse_csv_no_newlines(categories):
9797
prediction_file_stream.write(f'{i:030f},')
9898
prediction_file_stream.seek(0)
9999

100-
with pytest.raises(ScoreException, match=r'^No newlines detected in CSV\.$'):
100+
with pytest.raises(ScoreError, match=r'^No newlines detected in CSV\.$'):
101101
load_csv.parse_csv(prediction_file_stream, categories)
102102

103103

@@ -106,7 +106,7 @@ def test_parse_csv_empty(categories):
106106
prediction_file_stream = io.StringIO('\n\n')
107107

108108
with pytest.raises(
109-
ScoreException, match=r'^Could not parse CSV: "No columns to parse from file"\.$'
109+
ScoreError, match=r'^Could not parse CSV: "No columns to parse from file"\.$'
110110
):
111111
load_csv.parse_csv(prediction_file_stream, categories)
112112

@@ -115,7 +115,7 @@ def test_parse_csv_invalid_unicode(categories):
115115
prediction_file_stream = io.TextIOWrapper(io.BytesIO(b'\xef'))
116116

117117
with pytest.raises(
118-
ScoreException, match=r'^Could not parse CSV: could not decode file as UTF-8\.$'
118+
ScoreError, match=r'^Could not parse CSV: could not decode file as UTF-8\.$'
119119
):
120120
load_csv.parse_csv(prediction_file_stream, categories)
121121

@@ -129,7 +129,7 @@ def test_parse_csv_mismatched_headers(categories):
129129
)
130130

131131
# Pandas should drop extra columns without headers, but this is a common invalid case
132-
with pytest.raises(ScoreException, match=r'^Missing columns in CSV:'):
132+
with pytest.raises(ScoreError, match=r'^Missing columns in CSV:'):
133133
load_csv.parse_csv(prediction_file_stream, categories)
134134

135135

@@ -138,7 +138,7 @@ def test_parse_csv_missing_columns(categories):
138138
'image,MEL,BCC,AKIEC,BKL,DF\n' 'ISIC_0000123,1.0,0.0,0.0,0.0,0.0\n'
139139
)
140140

141-
with pytest.raises(ScoreException, match=r"^Missing columns in CSV: \['NV', 'VASC'\]\.$"):
141+
with pytest.raises(ScoreError, match=r"^Missing columns in CSV: \['NV', 'VASC'\]\.$"):
142142
load_csv.parse_csv(prediction_file_stream, categories)
143143

144144

@@ -148,7 +148,7 @@ def test_parse_csv_extra_columns(categories):
148148
'ISIC_0000123,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0\n'
149149
)
150150

151-
with pytest.raises(ScoreException, match=r"^Extra columns in CSV: \['BAZ', 'FOO'\]\.$"):
151+
with pytest.raises(ScoreError, match=r"^Extra columns in CSV: \['BAZ', 'FOO'\]\.$"):
152152
load_csv.parse_csv(prediction_file_stream, categories)
153153

154154

@@ -157,7 +157,7 @@ def test_parse_csv_misnamed_columns(categories):
157157
'image,MEL,FOO,BCC,AKIEC,BKL,BAZ,VASC\n' 'ISIC_0000123,1.0,0.0,0.0,0.0,0.0,0.0,0.0\n'
158158
)
159159

160-
with pytest.raises(ScoreException, match=r"^Missing columns in CSV: \['DF', 'NV'\]\.$"):
160+
with pytest.raises(ScoreError, match=r"^Missing columns in CSV: \['DF', 'NV'\]\.$"):
161161
load_csv.parse_csv(prediction_file_stream, categories)
162162

163163

@@ -200,7 +200,7 @@ def test_parse_csv_missing_index(categories):
200200
'MEL,NV,BCC,AKIEC,BKL,DF,VASC\n' '1.0,0.0,0.0,0.0,0.0,0.0,0.0\n'
201201
)
202202

203-
with pytest.raises(ScoreException, match=r"^Missing column in CSV: 'image'\.$"):
203+
with pytest.raises(ScoreError, match=r"^Missing column in CSV: 'image'\.$"):
204204
load_csv.parse_csv(prediction_file_stream, categories)
205205

206206

@@ -224,7 +224,7 @@ def test_parse_csv_missing_values(categories):
224224
)
225225

226226
with pytest.raises(
227-
ScoreException,
227+
ScoreError,
228228
match=r"^Missing value\(s\) in CSV for images: \['ISIC_0000124', 'ISIC_0000125'\]\.$",
229229
):
230230
load_csv.parse_csv(prediction_file_stream, categories)
@@ -239,7 +239,7 @@ def test_parse_csv_non_float_columns(categories):
239239
)
240240

241241
with pytest.raises(
242-
ScoreException,
242+
ScoreError,
243243
match=r"^CSV contains non-floating-point value\(s\) in columns: \['BCC', 'VASC'\]\.$",
244244
):
245245
load_csv.parse_csv(prediction_file_stream, categories)
@@ -254,7 +254,7 @@ def test_parse_csv_out_of_range_values(categories):
254254
)
255255

256256
with pytest.raises(
257-
ScoreException,
257+
ScoreError,
258258
match=r'^Values in CSV are outside the interval \[0\.0, 1\.0\] for images: '
259259
r"\['ISIC_0000123', 'ISIC_0000125'\]\.$",
260260
):
@@ -271,7 +271,7 @@ def test_parse_csv_duplicate_images(categories):
271271
)
272272

273273
with pytest.raises(
274-
ScoreException,
274+
ScoreError,
275275
match=r"^Duplicate image rows detected in CSV: \['ISIC_0000123', 'ISIC_0000124'\]\.$",
276276
):
277277
load_csv.parse_csv(prediction_file_stream, categories)
@@ -298,7 +298,7 @@ def test_validate_rows_missing_images(categories):
298298
prediction_probabilities = pd.DataFrame(
299299
[[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]], index=['ISIC_0000123'], columns=categories
300300
)
301-
with pytest.raises(ScoreException, match=r"^Missing images in CSV: \['ISIC_0000124'\]\.$"):
301+
with pytest.raises(ScoreError, match=r"^Missing images in CSV: \['ISIC_0000124'\]\.$"):
302302
load_csv.validate_rows(truth_probabilities, prediction_probabilities)
303303

304304
truth_probabilities = pd.DataFrame(
@@ -310,7 +310,7 @@ def test_validate_rows_missing_images(categories):
310310
[[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]], index=['ISIC_0000120'], columns=categories
311311
)
312312
with pytest.raises(
313-
ScoreException, match=r"^Missing images in CSV: \['ISIC_0000123', 'ISIC_0000124'\]\.$"
313+
ScoreError, match=r"^Missing images in CSV: \['ISIC_0000123', 'ISIC_0000124'\]\.$"
314314
):
315315
load_csv.validate_rows(truth_probabilities, prediction_probabilities)
316316

@@ -324,7 +324,7 @@ def test_validate_rows_missing_images(categories):
324324
index=['ISIC_0000123', 'ISIC_0000125'],
325325
columns=categories,
326326
)
327-
with pytest.raises(ScoreException, match=r"^Missing images in CSV: \['ISIC_0000124'\]\.$"):
327+
with pytest.raises(ScoreError, match=r"^Missing images in CSV: \['ISIC_0000124'\]\.$"):
328328
load_csv.validate_rows(truth_probabilities, prediction_probabilities)
329329

330330

@@ -342,7 +342,7 @@ def test_validate_rows_extra_images(categories):
342342
columns=categories,
343343
)
344344
with pytest.raises(
345-
ScoreException, match=r"^Extra images in CSV: \['ISIC_0000126', 'ISIC_0000127'\]\.$"
345+
ScoreError, match=r"^Extra images in CSV: \['ISIC_0000126', 'ISIC_0000127'\]\.$"
346346
):
347347
load_csv.validate_rows(truth_probabilities, prediction_probabilities)
348348

tests/test_load_image.py

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

33
import pytest
44

5-
from isic_challenge_scoring import ScoreException, load_image
5+
from isic_challenge_scoring import ScoreError, load_image
66

77

88
@pytest.mark.parametrize(
@@ -43,5 +43,5 @@ def test_load_segmentation_image_valid(test_images_path, test_image_name):
4343
@pytest.mark.parametrize('test_image_name', ['empty.png', 'random_bytes.png', 'rgb.png'])
4444
def test_load_segmentation_image_invalid(test_images_path, test_image_name):
4545
image_path = test_images_path / test_image_name
46-
with pytest.raises(ScoreException):
46+
with pytest.raises(ScoreError):
4747
load_image.load_segmentation_image(image_path)

0 commit comments

Comments
 (0)