segmodels-keras 0.1.0.dev8__tar.gz → 0.1.0.dev10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/PKG-INFO +1 -1
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/pyproject.toml +4 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/__init__.py +20 -16
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/_compat.py +2 -0
- segmodels_keras-0.1.0.dev10/segmodels_keras/_version.py +2 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/backbones/_weights.py +3 -1
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/backbones/backbones_factory.py +2 -2
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/backbones/inception_resnet_v2.py +3 -2
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/backbones/inception_v3.py +4 -2
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/backbones/resnet_18_34.py +35 -38
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/base/functional.py +9 -5
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/base/objects.py +26 -23
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/losses.py +27 -19
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/metrics.py +37 -35
- segmodels_keras-0.1.0.dev10/segmodels_keras/models/__init__.py +5 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/models/fpn.py +48 -35
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/models/linknet.py +55 -34
- segmodels_keras-0.1.0.dev10/segmodels_keras/models/model_factory.py +96 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/models/pspnet.py +53 -37
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/models/unet.py +51 -33
- segmodels_keras-0.1.0.dev10/segmodels_keras/utils.py +311 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras.egg-info/PKG-INFO +1 -1
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras.egg-info/SOURCES.txt +3 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/setup.py +2 -1
- segmodels_keras-0.1.0.dev10/tests/test_helper.py +3 -0
- segmodels_keras-0.1.0.dev10/tests/test_models.py +115 -0
- segmodels_keras-0.1.0.dev10/tests/test_utils.py +226 -0
- segmodels_keras-0.1.0.dev8/segmodels_keras/_version.py +0 -2
- segmodels_keras-0.1.0.dev8/segmodels_keras/utils.py +0 -117
- segmodels_keras-0.1.0.dev8/tests/test_models.py +0 -128
- segmodels_keras-0.1.0.dev8/tests/test_utils.py +0 -150
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/LICENSE +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/MANIFEST.in +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/README.rst +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/backbones/__init__.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/backbones/_common_blocks.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/backbones/resnet_common.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/base/__init__.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/models/_common_blocks.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/models/_utils.py +0 -0
- /segmodels_keras-0.1.0.dev8/segmodels_keras/models/__init__.py → /segmodels_keras-0.1.0.dev10/segmodels_keras/py.typed +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras.egg-info/dependency_links.txt +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras.egg-info/requires.txt +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras.egg-info/top_level.txt +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/setup.cfg +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/tests/test_backbones.py +0 -0
- {segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/tests/test_metrics.py +0 -0
|
@@ -1,35 +1,37 @@
|
|
|
1
1
|
import functools # noqa: I001
|
|
2
2
|
import os
|
|
3
|
+
from typing import Any
|
|
4
|
+
from collections.abc import Callable
|
|
3
5
|
|
|
4
6
|
# __version__ should be defined as soon as possible
|
|
5
7
|
from ._version import __version__
|
|
6
8
|
|
|
7
9
|
from . import base
|
|
8
10
|
|
|
9
|
-
_KERAS_FRAMEWORK_NAME = "keras"
|
|
10
|
-
_TF_KERAS_FRAMEWORK_NAME = "tf.keras"
|
|
11
|
+
_KERAS_FRAMEWORK_NAME: str = "keras"
|
|
12
|
+
_TF_KERAS_FRAMEWORK_NAME: str = "tf.keras"
|
|
11
13
|
|
|
12
|
-
_DEFAULT_KERAS_FRAMEWORK = _KERAS_FRAMEWORK_NAME
|
|
13
|
-
_KERAS_FRAMEWORK = None
|
|
14
|
-
_KERAS_BACKEND = None
|
|
15
|
-
_KERAS_LAYERS = None
|
|
16
|
-
_KERAS_MODELS = None
|
|
17
|
-
_KERAS_UTILS = None
|
|
18
|
-
_KERAS_LOSSES = None
|
|
14
|
+
_DEFAULT_KERAS_FRAMEWORK: str = _KERAS_FRAMEWORK_NAME
|
|
15
|
+
_KERAS_FRAMEWORK: str | None = None
|
|
16
|
+
_KERAS_BACKEND: Any = None
|
|
17
|
+
_KERAS_LAYERS: Any = None
|
|
18
|
+
_KERAS_MODELS: Any = None
|
|
19
|
+
_KERAS_UTILS: Any = None
|
|
20
|
+
_KERAS_LOSSES: Any = None
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
def inject_global_losses(func):
|
|
23
|
+
def inject_global_losses(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
22
24
|
@functools.wraps(func)
|
|
23
|
-
def wrapper(*args, **kwargs):
|
|
25
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
24
26
|
kwargs["losses"] = _KERAS_LOSSES
|
|
25
27
|
return func(*args, **kwargs)
|
|
26
28
|
|
|
27
29
|
return wrapper
|
|
28
30
|
|
|
29
31
|
|
|
30
|
-
def filter_kwargs(func):
|
|
32
|
+
def filter_kwargs(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
31
33
|
@functools.wraps(func)
|
|
32
|
-
def wrapper(*args, **kwargs):
|
|
34
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
33
35
|
new_kwargs = {
|
|
34
36
|
k: v
|
|
35
37
|
for k, v in kwargs.items()
|
|
@@ -40,12 +42,12 @@ def filter_kwargs(func):
|
|
|
40
42
|
return wrapper
|
|
41
43
|
|
|
42
44
|
|
|
43
|
-
def framework():
|
|
45
|
+
def framework() -> str | None:
|
|
44
46
|
"""Return name of Segmentation Models framework"""
|
|
45
47
|
return _KERAS_FRAMEWORK
|
|
46
48
|
|
|
47
49
|
|
|
48
|
-
def set_framework(name):
|
|
50
|
+
def set_framework(name: str) -> None:
|
|
49
51
|
"""Set framework for Segmentation Models
|
|
50
52
|
|
|
51
53
|
Args:
|
|
@@ -108,13 +110,14 @@ from . import losses, metrics, utils
|
|
|
108
110
|
from .backbones.backbones_factory import Backbones
|
|
109
111
|
from .models.fpn import FPN
|
|
110
112
|
from .models.linknet import Linknet
|
|
113
|
+
from .models.model_factory import get_model
|
|
111
114
|
from .models.pspnet import PSPNet
|
|
112
115
|
from .models.unet import Unet
|
|
113
116
|
|
|
114
117
|
get_available_backbone_names = Backbones.models_names
|
|
115
118
|
|
|
116
119
|
|
|
117
|
-
def get_preprocessing(name):
|
|
120
|
+
def get_preprocessing(name: str) -> Callable[[Any], Any]:
|
|
118
121
|
return Backbones.get_preprocessing(name)
|
|
119
122
|
|
|
120
123
|
|
|
@@ -126,6 +129,7 @@ __all__ = [
|
|
|
126
129
|
"__version__",
|
|
127
130
|
"framework",
|
|
128
131
|
"get_available_backbone_names",
|
|
132
|
+
"get_model",
|
|
129
133
|
"get_preprocessing",
|
|
130
134
|
"losses",
|
|
131
135
|
"metrics",
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/backbones/_weights.py
RENAMED
|
@@ -5,6 +5,8 @@ Source: https://github.com/qubvel/classification_models
|
|
|
5
5
|
|
|
6
6
|
from keras.utils import get_file
|
|
7
7
|
|
|
8
|
+
from segmodels_keras.utils import load_weights
|
|
9
|
+
|
|
8
10
|
__all__ = ["load_model_weights"]
|
|
9
11
|
|
|
10
12
|
|
|
@@ -35,7 +37,7 @@ def load_model_weights(model, model_name, dataset, classes, include_top):
|
|
|
35
37
|
md5_hash=weights["md5"],
|
|
36
38
|
)
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
load_weights(model, weights_path)
|
|
39
41
|
|
|
40
42
|
else:
|
|
41
43
|
raise ValueError(
|
|
@@ -24,14 +24,14 @@ class BackbonesFactory:
|
|
|
24
24
|
resnet_18_34.ResNet18,
|
|
25
25
|
resnet_18_34.preprocess_input,
|
|
26
26
|
# resnet18: layer1=2, layer2=2, layer3=2, layer4=2 blocks
|
|
27
|
-
# backbone output = conv5_block2_out (512ch); skips are the 4 stages before
|
|
27
|
+
# backbone output = conv5_block2_out (512ch); skips are the 4 stages before
|
|
28
28
|
("conv4_block2_out", "conv3_block2_out", "conv2_block2_out", "conv1_relu"),
|
|
29
29
|
),
|
|
30
30
|
"resnet34": (
|
|
31
31
|
resnet_18_34.ResNet34,
|
|
32
32
|
resnet_18_34.preprocess_input,
|
|
33
33
|
# resnet34: layer1=3, layer2=4, layer3=6, layer4=3 blocks
|
|
34
|
-
# backbone output = conv5_block3_out (512ch); skips are the 4 stages before
|
|
34
|
+
# backbone output = conv5_block3_out (512ch); skips are the 4 stages before
|
|
35
35
|
("conv4_block6_out", "conv3_block4_out", "conv2_block3_out", "conv1_relu"),
|
|
36
36
|
),
|
|
37
37
|
# ResNets > 50 layers are available via keras.applications, so use those.
|
|
@@ -38,6 +38,7 @@ from keras import backend, layers, models
|
|
|
38
38
|
from keras.applications import imagenet_utils
|
|
39
39
|
from keras.utils import get_file, get_source_inputs
|
|
40
40
|
|
|
41
|
+
from ..utils import load_weights
|
|
41
42
|
from .resnet_common import _obtain_input_shape
|
|
42
43
|
|
|
43
44
|
BASE_WEIGHT_URL = (
|
|
@@ -261,9 +262,9 @@ def InceptionResNetV2(
|
|
|
261
262
|
cache_subdir="models",
|
|
262
263
|
file_hash="d19885ff4a710c122648d3b5c3b684e4",
|
|
263
264
|
)
|
|
264
|
-
|
|
265
|
+
load_weights(model, weights_path)
|
|
265
266
|
elif weights is not None:
|
|
266
|
-
|
|
267
|
+
load_weights(model, weights)
|
|
267
268
|
|
|
268
269
|
return model
|
|
269
270
|
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/backbones/inception_v3.py
RENAMED
|
@@ -14,6 +14,8 @@ from keras import backend, layers, models
|
|
|
14
14
|
from keras import utils as keras_utils
|
|
15
15
|
from keras.applications import imagenet_utils
|
|
16
16
|
|
|
17
|
+
from ..utils import load_weights
|
|
18
|
+
|
|
17
19
|
WEIGHTS_PATH = (
|
|
18
20
|
"https://github.com/fchollet/deep-learning-models/"
|
|
19
21
|
"releases/download/v0.5/"
|
|
@@ -361,9 +363,9 @@ def InceptionV3(
|
|
|
361
363
|
cache_subdir="models",
|
|
362
364
|
file_hash="bcbd6486424b2319ff4ef7d526e38f63",
|
|
363
365
|
)
|
|
364
|
-
|
|
366
|
+
load_weights(model, weights_path)
|
|
365
367
|
elif weights is not None:
|
|
366
|
-
|
|
368
|
+
load_weights(model, weights)
|
|
367
369
|
|
|
368
370
|
return model
|
|
369
371
|
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/backbones/resnet_18_34.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"""ResNet18 and ResNet34 Keras models.
|
|
2
2
|
|
|
3
3
|
Complements keras.applications (which provides ResNet50+) with BasicBlock-based
|
|
4
4
|
ResNet18 and ResNet34. The implementation follows the keras.applications structure
|
|
@@ -14,27 +14,22 @@ closely so that the two can be maintained together:
|
|
|
14
14
|
Layer naming follows the same ``conv{N}_block{M}_*`` convention used by
|
|
15
15
|
keras.applications, so models built here share the same weight-loading patterns.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
the
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
weights.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
is actually ``momentum=0.1``, but after inversion we use ``momentum=0.9`` here.
|
|
34
|
-
- The single bottom convolutional layer gets ``use_bias=False`` (like all convolutional
|
|
35
|
-
layers in the model just like the version in torchvision and in segmentation_models.
|
|
36
|
-
In the keras.applications ResNet(50+) implementation ``use_bias=True`` is used for the
|
|
37
|
-
single bottom convolutional layer.
|
|
17
|
+
Notes:
|
|
18
|
+
- The resulting resnet18 and resnet34 are not exactly the same as the version in
|
|
19
|
+
https://github.com/qubvel/classification_models and hence is also different than
|
|
20
|
+
the segmentation models in https://github.com/qubvel/segmentation_models.
|
|
21
|
+
There are some differences in the bottom and top layers of the resnet, e.g. because
|
|
22
|
+
the classification_models version applies also post-activation after the last
|
|
23
|
+
residual block.
|
|
24
|
+
- The implementation is also ~ compatible with torchvision ResNet18/34 pretrained
|
|
25
|
+
weights based on a (rough) test. There are some small differences, but they don't
|
|
26
|
+
seem to influance the results significantly:
|
|
27
|
+
- BatchNormalization in torchvision uses ``momentum=0.9`` and ``epsilon=1e-5``
|
|
28
|
+
instead of the keras.applications defaults of ``momentum=0.99`` and
|
|
29
|
+
``epsilon=1.001e-5`` used here.
|
|
30
|
+
Note that momentum is only used during training. In addition, the value in
|
|
31
|
+
pytorch is inverted compared to the tf.keras implementation, so the default in
|
|
32
|
+
pytorch is actually ``momentum=0.1``, but inverted this gives ``momentum=0.9``.
|
|
38
33
|
"""
|
|
39
34
|
|
|
40
35
|
import os
|
|
@@ -43,8 +38,12 @@ from keras import backend, layers, models
|
|
|
43
38
|
from keras.applications import imagenet_utils
|
|
44
39
|
from keras.utils import get_source_inputs
|
|
45
40
|
|
|
41
|
+
from ..utils import load_weights
|
|
46
42
|
from .resnet_common import _obtain_input_shape
|
|
47
43
|
|
|
44
|
+
momentum = 0.99
|
|
45
|
+
epsilon = 1.001e-5
|
|
46
|
+
|
|
48
47
|
|
|
49
48
|
def ResNet(
|
|
50
49
|
stack_fn,
|
|
@@ -141,7 +140,7 @@ def ResNet(
|
|
|
141
140
|
# The batchnormalization parameters have been changed to be compatible with the
|
|
142
141
|
# torchvision ResNet18/34 pretrained weights.
|
|
143
142
|
x = layers.BatchNormalization(
|
|
144
|
-
axis=bn_axis, momentum=
|
|
143
|
+
axis=bn_axis, momentum=momentum, epsilon=epsilon, name="conv1_bn"
|
|
145
144
|
)(x)
|
|
146
145
|
x = layers.Activation("relu", name="conv1_relu")(x)
|
|
147
146
|
|
|
@@ -154,7 +153,7 @@ def ResNet(
|
|
|
154
153
|
# The batchnormalization parameters have been changed to be compatible with the
|
|
155
154
|
# torchvision ResNet18/34 pretrained weights.
|
|
156
155
|
x = layers.BatchNormalization(
|
|
157
|
-
axis=bn_axis, momentum=
|
|
156
|
+
axis=bn_axis, momentum=momentum, epsilon=epsilon, name="post_bn"
|
|
158
157
|
)(x)
|
|
159
158
|
x = layers.Activation("relu", name="post_relu")(x)
|
|
160
159
|
|
|
@@ -184,7 +183,7 @@ def ResNet(
|
|
|
184
183
|
model = models.Model(inputs, x, name=name)
|
|
185
184
|
|
|
186
185
|
if weights is not None and os.path.exists(weights):
|
|
187
|
-
|
|
186
|
+
load_weights(model, weights)
|
|
188
187
|
|
|
189
188
|
return model
|
|
190
189
|
|
|
@@ -226,7 +225,7 @@ def residual_basicblock(
|
|
|
226
225
|
filters, 1, strides=stride, use_bias=False, name=f"{name}_0_conv"
|
|
227
226
|
)(x)
|
|
228
227
|
shortcut = layers.BatchNormalization(
|
|
229
|
-
axis=bn_axis, momentum=
|
|
228
|
+
axis=bn_axis, momentum=momentum, epsilon=epsilon, name=f"{name}_0_bn"
|
|
230
229
|
)(shortcut)
|
|
231
230
|
else:
|
|
232
231
|
shortcut = x
|
|
@@ -236,14 +235,14 @@ def residual_basicblock(
|
|
|
236
235
|
filters, kernel_size, strides=stride, use_bias=False, name=f"{name}_1_conv"
|
|
237
236
|
)(x)
|
|
238
237
|
x = layers.BatchNormalization(
|
|
239
|
-
axis=bn_axis, momentum=
|
|
238
|
+
axis=bn_axis, momentum=momentum, epsilon=epsilon, name=f"{name}_1_bn"
|
|
240
239
|
)(x)
|
|
241
240
|
x = layers.Activation("relu", name=f"{name}_1_relu")(x)
|
|
242
241
|
|
|
243
242
|
x = layers.ZeroPadding2D(padding=((1, 1), (1, 1)), name=f"{name}_2_pad")(x)
|
|
244
243
|
x = layers.Conv2D(filters, kernel_size, use_bias=False, name=f"{name}_2_conv")(x)
|
|
245
244
|
x = layers.BatchNormalization(
|
|
246
|
-
axis=bn_axis, momentum=
|
|
245
|
+
axis=bn_axis, momentum=momentum, epsilon=epsilon, name=f"{name}_2_bn"
|
|
247
246
|
)(x)
|
|
248
247
|
|
|
249
248
|
x = layers.Add(name=f"{name}_add")([shortcut, x])
|
|
@@ -354,15 +353,13 @@ def ResNet34(
|
|
|
354
353
|
)
|
|
355
354
|
|
|
356
355
|
|
|
357
|
-
def preprocess_input(x,
|
|
358
|
-
"""
|
|
359
|
-
import numpy as np
|
|
356
|
+
def preprocess_input(x, data_format=None):
|
|
357
|
+
"""Preprocesses a numpy array encoding a batch of images.
|
|
360
358
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
std = np.array([52.17, 45.38, 44, 39.69, 79.3], dtype="float32")
|
|
359
|
+
Args:
|
|
360
|
+
x: a 4D numpy array consists of RGB values within [0, 255].
|
|
364
361
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return (x
|
|
362
|
+
Returns:
|
|
363
|
+
Preprocessed array.
|
|
364
|
+
"""
|
|
365
|
+
return imagenet_utils.preprocess_input(x, data_format=data_format, mode="tf")
|
{segmodels_keras-0.1.0.dev8 → segmodels_keras-0.1.0.dev10}/segmodels_keras/base/functional.py
RENAMED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
1
3
|
import keras
|
|
2
4
|
|
|
3
5
|
from segmodels_keras._compat import KERAS_GTE_3
|
|
@@ -7,7 +9,7 @@ if KERAS_GTE_3:
|
|
|
7
9
|
else:
|
|
8
10
|
from keras import backend as ops
|
|
9
11
|
|
|
10
|
-
SMOOTH = 1e-5
|
|
12
|
+
SMOOTH: float = 1e-5
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
# ----------------------------------------------------------------
|
|
@@ -15,7 +17,7 @@ SMOOTH = 1e-5
|
|
|
15
17
|
# ----------------------------------------------------------------
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
def _gather_channels(x, indexes, **kwargs):
|
|
20
|
+
def _gather_channels(x: Any, indexes: Any, **kwargs: Any) -> Any:
|
|
19
21
|
backend = kwargs["backend"]
|
|
20
22
|
|
|
21
23
|
"""Slice tensor along channels axis by given indexes"""
|
|
@@ -30,7 +32,7 @@ def _gather_channels(x, indexes, **kwargs):
|
|
|
30
32
|
return x
|
|
31
33
|
|
|
32
34
|
|
|
33
|
-
def get_reduce_axes(per_image, **kwargs):
|
|
35
|
+
def get_reduce_axes(per_image: bool, **kwargs: Any) -> list[int]:
|
|
34
36
|
backend = kwargs["backend"]
|
|
35
37
|
|
|
36
38
|
axes = [1, 2] if backend.image_data_format() == "channels_last" else [2, 3]
|
|
@@ -39,13 +41,15 @@ def get_reduce_axes(per_image, **kwargs):
|
|
|
39
41
|
return axes
|
|
40
42
|
|
|
41
43
|
|
|
42
|
-
def gather_channels(
|
|
44
|
+
def gather_channels(
|
|
45
|
+
*xs: Any, indexes: int | list[int] | None = None, **kwargs: Any
|
|
46
|
+
) -> tuple[Any, ...] | tuple[Any]:
|
|
43
47
|
"""Slice tensors along channels axis by given indexes"""
|
|
44
48
|
if indexes is None:
|
|
45
49
|
return xs
|
|
46
50
|
elif isinstance(indexes, (int)):
|
|
47
51
|
indexes = [indexes]
|
|
48
|
-
xs =
|
|
52
|
+
xs = tuple(_gather_channels(x, indexes=indexes, **kwargs) for x in xs)
|
|
49
53
|
return xs
|
|
50
54
|
|
|
51
55
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
class KerasObject:
|
|
2
|
-
_backend = None
|
|
3
|
-
_models = None
|
|
4
|
-
_layers = None
|
|
5
|
-
_utils = None
|
|
5
|
+
_backend: Any = None
|
|
6
|
+
_models: Any = None
|
|
7
|
+
_layers: Any = None
|
|
8
|
+
_utils: Any = None
|
|
6
9
|
|
|
7
|
-
def __init__(self, name=None):
|
|
10
|
+
def __init__(self, name: str | None = None) -> None:
|
|
8
11
|
if (
|
|
9
12
|
self.backend is None
|
|
10
13
|
or self.utils is None
|
|
@@ -16,28 +19,28 @@ class KerasObject:
|
|
|
16
19
|
self._name = name
|
|
17
20
|
|
|
18
21
|
@property
|
|
19
|
-
def __name__(self):
|
|
22
|
+
def __name__(self) -> str:
|
|
20
23
|
if self._name is None:
|
|
21
|
-
return self.__class__.__name__
|
|
24
|
+
return self.__class__.__name__ # type: ignore[return-value]
|
|
22
25
|
return self._name
|
|
23
26
|
|
|
24
27
|
@property
|
|
25
|
-
def name(self):
|
|
28
|
+
def name(self) -> str:
|
|
26
29
|
return self.__name__
|
|
27
30
|
|
|
28
31
|
@name.setter
|
|
29
|
-
def name(self, name):
|
|
32
|
+
def name(self, name: str) -> None:
|
|
30
33
|
self._name = name
|
|
31
34
|
|
|
32
35
|
@classmethod
|
|
33
|
-
def set_submodules(cls, backend, layers, models, utils):
|
|
36
|
+
def set_submodules(cls, backend: Any, layers: Any, models: Any, utils: Any) -> None:
|
|
34
37
|
cls._backend = backend
|
|
35
38
|
cls._layers = layers
|
|
36
39
|
cls._models = models
|
|
37
40
|
cls._utils = utils
|
|
38
41
|
|
|
39
42
|
@property
|
|
40
|
-
def submodules(self):
|
|
43
|
+
def submodules(self) -> dict[str, Any]:
|
|
41
44
|
return {
|
|
42
45
|
"backend": self.backend,
|
|
43
46
|
"layers": self.layers,
|
|
@@ -46,19 +49,19 @@ class KerasObject:
|
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
@property
|
|
49
|
-
def backend(self):
|
|
52
|
+
def backend(self) -> Any:
|
|
50
53
|
return self._backend
|
|
51
54
|
|
|
52
55
|
@property
|
|
53
|
-
def layers(self):
|
|
56
|
+
def layers(self) -> Any:
|
|
54
57
|
return self._layers
|
|
55
58
|
|
|
56
59
|
@property
|
|
57
|
-
def models(self):
|
|
60
|
+
def models(self) -> Any:
|
|
58
61
|
return self._models
|
|
59
62
|
|
|
60
63
|
@property
|
|
61
|
-
def utils(self):
|
|
64
|
+
def utils(self) -> Any:
|
|
62
65
|
return self._utils
|
|
63
66
|
|
|
64
67
|
|
|
@@ -67,27 +70,27 @@ class Metric(KerasObject):
|
|
|
67
70
|
|
|
68
71
|
|
|
69
72
|
class Loss(KerasObject):
|
|
70
|
-
def __add__(self, other):
|
|
73
|
+
def __add__(self, other: Any) -> Any:
|
|
71
74
|
if isinstance(other, Loss):
|
|
72
75
|
return SumOfLosses(self, other)
|
|
73
76
|
else:
|
|
74
77
|
raise ValueError("Loss should be inherited from `Loss` class")
|
|
75
78
|
|
|
76
|
-
def __radd__(self, other):
|
|
79
|
+
def __radd__(self, other: Any) -> Any:
|
|
77
80
|
return self.__add__(other)
|
|
78
81
|
|
|
79
|
-
def __mul__(self, value):
|
|
82
|
+
def __mul__(self, value: int | float) -> Any:
|
|
80
83
|
if isinstance(value, (int, float)):
|
|
81
84
|
return MultipliedLoss(self, value)
|
|
82
85
|
else:
|
|
83
86
|
raise ValueError("Loss should be inherited from `BaseLoss` class")
|
|
84
87
|
|
|
85
|
-
def __rmul__(self, other):
|
|
88
|
+
def __rmul__(self, other: Any) -> Any:
|
|
86
89
|
return self.__mul__(other)
|
|
87
90
|
|
|
88
91
|
|
|
89
92
|
class MultipliedLoss(Loss):
|
|
90
|
-
def __init__(self, loss, multiplier):
|
|
93
|
+
def __init__(self, loss: Any, multiplier: int | float) -> None:
|
|
91
94
|
|
|
92
95
|
# resolve name
|
|
93
96
|
if len(loss.__name__.split("+")) > 1:
|
|
@@ -98,16 +101,16 @@ class MultipliedLoss(Loss):
|
|
|
98
101
|
self.loss = loss
|
|
99
102
|
self.multiplier = multiplier
|
|
100
103
|
|
|
101
|
-
def __call__(self, gt, pr):
|
|
104
|
+
def __call__(self, gt: Any, pr: Any) -> Any:
|
|
102
105
|
return self.multiplier * self.loss(gt, pr)
|
|
103
106
|
|
|
104
107
|
|
|
105
108
|
class SumOfLosses(Loss):
|
|
106
|
-
def __init__(self, l1, l2):
|
|
109
|
+
def __init__(self, l1: Any, l2: Any) -> None:
|
|
107
110
|
name = f"{l1.__name__}_plus_{l2.__name__}"
|
|
108
111
|
super().__init__(name=name)
|
|
109
112
|
self.l1 = l1
|
|
110
113
|
self.l2 = l2
|
|
111
114
|
|
|
112
|
-
def __call__(self, gt, pr):
|
|
115
|
+
def __call__(self, gt: Any, pr: Any) -> Any:
|
|
113
116
|
return self.l1(gt, pr) + self.l2(gt, pr)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
1
3
|
from .base import Loss
|
|
2
4
|
from .base import functional as F
|
|
3
5
|
|
|
4
|
-
SMOOTH = 1e-5
|
|
6
|
+
SMOOTH: float = 1e-5
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class JaccardLoss(Loss):
|
|
@@ -32,8 +34,12 @@ class JaccardLoss(Loss):
|
|
|
32
34
|
"""
|
|
33
35
|
|
|
34
36
|
def __init__(
|
|
35
|
-
self,
|
|
36
|
-
|
|
37
|
+
self,
|
|
38
|
+
class_weights: Any = None,
|
|
39
|
+
class_indexes: Any = None,
|
|
40
|
+
per_image: bool = False,
|
|
41
|
+
smooth: float = SMOOTH,
|
|
42
|
+
) -> None:
|
|
37
43
|
super().__init__(name="jaccard_loss")
|
|
38
44
|
self.class_weights = class_weights if class_weights is not None else 1
|
|
39
45
|
self.class_indexes = class_indexes
|
|
@@ -92,12 +98,12 @@ class DiceLoss(Loss):
|
|
|
92
98
|
|
|
93
99
|
def __init__(
|
|
94
100
|
self,
|
|
95
|
-
beta=1,
|
|
96
|
-
class_weights=None,
|
|
97
|
-
class_indexes=None,
|
|
98
|
-
per_image=False,
|
|
99
|
-
smooth=SMOOTH,
|
|
100
|
-
):
|
|
101
|
+
beta: int | float = 1,
|
|
102
|
+
class_weights: Any = None,
|
|
103
|
+
class_indexes: Any = None,
|
|
104
|
+
per_image: bool = False,
|
|
105
|
+
smooth: float = SMOOTH,
|
|
106
|
+
) -> None:
|
|
101
107
|
super().__init__(name="dice_loss")
|
|
102
108
|
self.beta = beta
|
|
103
109
|
self.class_weights = class_weights if class_weights is not None else 1
|
|
@@ -137,7 +143,7 @@ class BinaryCELoss(Loss):
|
|
|
137
143
|
model.compile('SGD', loss=loss)
|
|
138
144
|
"""
|
|
139
145
|
|
|
140
|
-
def __init__(self):
|
|
146
|
+
def __init__(self) -> None:
|
|
141
147
|
super().__init__(name="binary_crossentropy")
|
|
142
148
|
|
|
143
149
|
def __call__(self, gt, pr):
|
|
@@ -168,7 +174,7 @@ class CategoricalCELoss(Loss):
|
|
|
168
174
|
model.compile('SGD', loss=loss)
|
|
169
175
|
"""
|
|
170
176
|
|
|
171
|
-
def __init__(self, class_weights=None, class_indexes=None):
|
|
177
|
+
def __init__(self, class_weights: Any = None, class_indexes: Any = None) -> None:
|
|
172
178
|
super().__init__(name="categorical_crossentropy")
|
|
173
179
|
self.class_weights = class_weights if class_weights is not None else 1
|
|
174
180
|
self.class_indexes = class_indexes
|
|
@@ -209,7 +215,9 @@ class CategoricalFocalLoss(Loss):
|
|
|
209
215
|
model.compile('SGD', loss=loss)
|
|
210
216
|
"""
|
|
211
217
|
|
|
212
|
-
def __init__(
|
|
218
|
+
def __init__(
|
|
219
|
+
self, alpha: float = 0.25, gamma: float = 2.0, class_indexes: Any = None
|
|
220
|
+
) -> None:
|
|
213
221
|
super().__init__(name="focal_loss")
|
|
214
222
|
self.alpha = alpha
|
|
215
223
|
self.gamma = gamma
|
|
@@ -250,7 +258,7 @@ class BinaryFocalLoss(Loss):
|
|
|
250
258
|
model.compile('SGD', loss=loss)
|
|
251
259
|
""" # noqa: E501
|
|
252
260
|
|
|
253
|
-
def __init__(self, alpha=0.25, gamma=2.0):
|
|
261
|
+
def __init__(self, alpha: float = 0.25, gamma: float = 2.0) -> None:
|
|
254
262
|
super().__init__(name="binary_focal_loss")
|
|
255
263
|
self.alpha = alpha
|
|
256
264
|
self.gamma = gamma
|
|
@@ -262,14 +270,14 @@ class BinaryFocalLoss(Loss):
|
|
|
262
270
|
|
|
263
271
|
|
|
264
272
|
# aliases
|
|
265
|
-
jaccard_loss = JaccardLoss()
|
|
266
|
-
dice_loss = DiceLoss()
|
|
273
|
+
jaccard_loss: JaccardLoss = JaccardLoss()
|
|
274
|
+
dice_loss: DiceLoss = DiceLoss()
|
|
267
275
|
|
|
268
|
-
binary_focal_loss = BinaryFocalLoss()
|
|
269
|
-
categorical_focal_loss = CategoricalFocalLoss()
|
|
276
|
+
binary_focal_loss: BinaryFocalLoss = BinaryFocalLoss()
|
|
277
|
+
categorical_focal_loss: CategoricalFocalLoss = CategoricalFocalLoss()
|
|
270
278
|
|
|
271
|
-
binary_crossentropy = BinaryCELoss()
|
|
272
|
-
categorical_crossentropy = CategoricalCELoss()
|
|
279
|
+
binary_crossentropy: BinaryCELoss = BinaryCELoss()
|
|
280
|
+
categorical_crossentropy: CategoricalCELoss = CategoricalCELoss()
|
|
273
281
|
|
|
274
282
|
# loss combinations
|
|
275
283
|
bce_dice_loss = binary_crossentropy + dice_loss
|