segmodels-keras 0.1.0.dev9__tar.gz → 0.1.0.dev11__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.
Files changed (48) hide show
  1. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/PKG-INFO +1 -1
  2. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/pyproject.toml +5 -1
  3. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/__init__.py +25 -18
  4. segmodels_keras-0.1.0.dev11/segmodels_keras/_compat.py +6 -0
  5. segmodels_keras-0.1.0.dev11/segmodels_keras/_version.py +2 -0
  6. segmodels_keras-0.1.0.dev11/segmodels_keras/backbones/__init__.py +1 -0
  7. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/_common_blocks.py +6 -3
  8. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/_weights.py +2 -1
  9. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/backbones_factory.py +39 -6
  10. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/inception_resnet_v2.py +10 -5
  11. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/inception_v3.py +10 -3
  12. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/resnet_18_34.py +5 -4
  13. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/base/__init__.py +2 -0
  14. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/base/functional.py +44 -15
  15. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/base/objects.py +40 -24
  16. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/losses.py +47 -37
  17. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/metrics.py +59 -54
  18. segmodels_keras-0.1.0.dev11/segmodels_keras/models/__init__.py +5 -0
  19. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/_common_blocks.py +1 -2
  20. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/_utils.py +1 -1
  21. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/fpn.py +65 -49
  22. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/linknet.py +72 -48
  23. segmodels_keras-0.1.0.dev11/segmodels_keras/models/model_factory.py +96 -0
  24. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/pspnet.py +64 -48
  25. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/unet.py +79 -60
  26. segmodels_keras-0.1.0.dev11/segmodels_keras/utils.py +314 -0
  27. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras.egg-info/PKG-INFO +1 -1
  28. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras.egg-info/SOURCES.txt +3 -0
  29. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/setup.py +4 -1
  30. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/tests/test_backbones.py +2 -0
  31. segmodels_keras-0.1.0.dev11/tests/test_helper.py +5 -0
  32. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/tests/test_metrics.py +2 -0
  33. segmodels_keras-0.1.0.dev11/tests/test_models.py +117 -0
  34. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/tests/test_utils.py +17 -23
  35. segmodels_keras-0.1.0.dev9/segmodels_keras/_compat.py +0 -123
  36. segmodels_keras-0.1.0.dev9/segmodels_keras/_version.py +0 -2
  37. segmodels_keras-0.1.0.dev9/segmodels_keras/models/__init__.py +0 -0
  38. segmodels_keras-0.1.0.dev9/segmodels_keras/utils.py +0 -117
  39. segmodels_keras-0.1.0.dev9/tests/test_models.py +0 -128
  40. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/LICENSE +0 -0
  41. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/MANIFEST.in +0 -0
  42. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/README.rst +0 -0
  43. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/resnet_common.py +0 -0
  44. /segmodels_keras-0.1.0.dev9/segmodels_keras/backbones/__init__.py → /segmodels_keras-0.1.0.dev11/segmodels_keras/py.typed +0 -0
  45. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras.egg-info/dependency_links.txt +0 -0
  46. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras.egg-info/requires.txt +0 -0
  47. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/segmodels_keras.egg-info/top_level.txt +0 -0
  48. {segmodels_keras-0.1.0.dev9 → segmodels_keras-0.1.0.dev11}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: segmodels_keras
3
- Version: 0.1.0.dev9
3
+ Version: 0.1.0.dev11
4
4
  Summary: Image segmentation models with pre-trained backbones with Keras.
5
5
  Home-page: https://github.com/orthoseg/segmodels_keras
6
6
  Author: Pieter Roggemans
@@ -1,3 +1,7 @@
1
+ [[tool.mypy.overrides]]
2
+ module = "h5py.*,keras.*,six.*,setuptools.*,sphinx_rtd_theme.*,tensorflow.*"
3
+ ignore_missing_imports = true
4
+
1
5
  [tool.pyright]
2
6
  exclude = ["local_ignore", "**/node_modules", "**/__pycache__", "**/.*"]
3
7
  ignore = ["*"]
@@ -33,7 +37,7 @@ select = [
33
37
  "NPY", # NumPy-specific rules
34
38
  "E", # pycodestyle errors
35
39
  "W", # pycodestyle warnings
36
- # "D", # pydocstyle
40
+ "D", # pydocstyle
37
41
  "F", # pyflakes
38
42
  "PLC", # pylint convention
39
43
  "PLE", # pylint error
@@ -1,35 +1,39 @@
1
+ """Segmentation Models for Keras."""
2
+
1
3
  import functools # noqa: I001
2
4
  import os
5
+ from typing import Any
6
+ from collections.abc import Callable
3
7
 
4
8
  # __version__ should be defined as soon as possible
5
9
  from ._version import __version__
6
10
 
7
11
  from . import base
8
12
 
9
- _KERAS_FRAMEWORK_NAME = "keras"
10
- _TF_KERAS_FRAMEWORK_NAME = "tf.keras"
13
+ _KERAS_FRAMEWORK_NAME: str = "keras"
14
+ _TF_KERAS_FRAMEWORK_NAME: str = "tf.keras"
11
15
 
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
16
+ _DEFAULT_KERAS_FRAMEWORK: str = _KERAS_FRAMEWORK_NAME
17
+ _KERAS_FRAMEWORK: str | None = None
18
+ _KERAS_BACKEND: Any = None
19
+ _KERAS_LAYERS: Any = None
20
+ _KERAS_MODELS: Any = None
21
+ _KERAS_UTILS: Any = None
22
+ _KERAS_LOSSES: Any = None
19
23
 
20
24
 
21
- def inject_global_losses(func):
25
+ def inject_global_losses(func: Callable[..., Any]) -> Callable[..., Any]:
22
26
  @functools.wraps(func)
23
- def wrapper(*args, **kwargs):
27
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
24
28
  kwargs["losses"] = _KERAS_LOSSES
25
29
  return func(*args, **kwargs)
26
30
 
27
31
  return wrapper
28
32
 
29
33
 
30
- def filter_kwargs(func):
34
+ def filter_kwargs(func: Callable[..., Any]) -> Callable[..., Any]:
31
35
  @functools.wraps(func)
32
- def wrapper(*args, **kwargs):
36
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
33
37
  new_kwargs = {
34
38
  k: v
35
39
  for k, v in kwargs.items()
@@ -40,13 +44,13 @@ def filter_kwargs(func):
40
44
  return wrapper
41
45
 
42
46
 
43
- def framework():
44
- """Return name of Segmentation Models framework"""
47
+ def framework() -> str | None:
48
+ """Return name of Segmentation Models framework."""
45
49
  return _KERAS_FRAMEWORK
46
50
 
47
51
 
48
- def set_framework(name):
49
- """Set framework for Segmentation Models
52
+ def set_framework(name: str) -> None:
53
+ """Set framework for Segmentation Models.
50
54
 
51
55
  Args:
52
56
  name (str): one of ``keras``, ``tf.keras``, case insensitive.
@@ -108,13 +112,15 @@ from . import losses, metrics, utils
108
112
  from .backbones.backbones_factory import Backbones
109
113
  from .models.fpn import FPN
110
114
  from .models.linknet import Linknet
115
+ from .models.model_factory import get_model
111
116
  from .models.pspnet import PSPNet
112
117
  from .models.unet import Unet
113
118
 
114
119
  get_available_backbone_names = Backbones.models_names
115
120
 
116
121
 
117
- def get_preprocessing(name):
122
+ def get_preprocessing(name: str) -> Callable[[Any], Any]:
123
+ """Get the preprocessing function for a backbone by name."""
118
124
  return Backbones.get_preprocessing(name)
119
125
 
120
126
 
@@ -126,6 +132,7 @@ __all__ = [
126
132
  "__version__",
127
133
  "framework",
128
134
  "get_available_backbone_names",
135
+ "get_model",
129
136
  "get_preprocessing",
130
137
  "losses",
131
138
  "metrics",
@@ -0,0 +1,6 @@
1
+ """Compatibility utilities for Keras 3 and legacy Keras 2."""
2
+
3
+ import keras
4
+ from packaging.version import parse as parse_version
5
+
6
+ KERAS_GTE_3 = parse_version(keras.__version__) >= parse_version("3.0.0")
@@ -0,0 +1,2 @@
1
+ __versioninfo__: tuple[int, int, int, str] = (0, 1, 0, "dev11")
2
+ __version__: str = ".".join(map(str, __versioninfo__))
@@ -0,0 +1 @@
1
+ """Backbones package."""
@@ -44,6 +44,7 @@ def GroupConv2D(
44
44
  If you don't specify anything, no activation is applied
45
45
  (ie. "linear" activation: a(x) = x).
46
46
  padding: one of "valid" or "same" (case-insensitive).
47
+ kwargs: additional keyword arguments (unused).
47
48
 
48
49
  Input shape:
49
50
  4D tensor with shape: (batch, rows, cols, channels) if data_format is
@@ -97,12 +98,14 @@ def expand_dims(x, channels_axis):
97
98
 
98
99
 
99
100
  def ChannelSE(reduction=16, **kwargs): # noqa: ARG001
100
- """
101
- Squeeze and Excitation block, reimplementation inspired by
102
- https://github.com/Cadene/pretrained-models.pytorch/blob/master/pretrainedmodels/models/senet.py
101
+ """Squeeze and Excitation block.
102
+
103
+ Reimplementation inspired by
104
+ https://github.com/Cadene/pretrained-models.pytorch/blob/master/pretrainedmodels/models/senet.py.
103
105
 
104
106
  Args:
105
107
  reduction: channels squeeze factor
108
+ kwargs: additional keyword arguments (unused).
106
109
 
107
110
  """
108
111
  channels_axis = 3 if backend.image_data_format() == "channels_last" else 1
@@ -3,9 +3,10 @@
3
3
  Source: https://github.com/qubvel/classification_models
4
4
  """
5
5
 
6
- from segmodels_keras._compat import load_weights
7
6
  from keras.utils import get_file
8
7
 
8
+ from segmodels_keras.utils import load_weights
9
+
9
10
  __all__ = ["load_model_weights"]
10
11
 
11
12
 
@@ -1,3 +1,6 @@
1
+ """Backbones factory module."""
2
+
3
+ import keras
1
4
  import keras.applications as ka
2
5
 
3
6
  from . import inception_resnet_v2 as irv2
@@ -24,14 +27,14 @@ class BackbonesFactory:
24
27
  resnet_18_34.ResNet18,
25
28
  resnet_18_34.preprocess_input,
26
29
  # resnet18: layer1=2, layer2=2, layer3=2, layer4=2 blocks
27
- # backbone output = conv5_block2_out (512ch); skips are the 4 stages before it
30
+ # backbone output = conv5_block2_out (512ch); skips are the 4 stages before
28
31
  ("conv4_block2_out", "conv3_block2_out", "conv2_block2_out", "conv1_relu"),
29
32
  ),
30
33
  "resnet34": (
31
34
  resnet_18_34.ResNet34,
32
35
  resnet_18_34.preprocess_input,
33
36
  # resnet34: layer1=3, layer2=4, layer3=6, layer4=3 blocks
34
- # backbone output = conv5_block3_out (512ch); skips are the 4 stages before it
37
+ # backbone output = conv5_block3_out (512ch); skips are the 4 stages before
35
38
  ("conv4_block6_out", "conv3_block4_out", "conv2_block3_out", "conv1_relu"),
36
39
  ),
37
40
  # ResNets > 50 layers are available via keras.applications, so use those.
@@ -249,27 +252,54 @@ class BackbonesFactory:
249
252
  ),
250
253
  ],
251
254
  # EfficientNetV2
252
- # Skip layers from https://github.com/chinefed/segmentation_models_fork
255
+ # Stage layout differs from EfficientNet-Bx.
256
+ # Skip layers selected to match decoder resolutions:
257
+ # x16 -> x8 -> x4 -> x2
258
+ "efficientnetv2s": (
259
+ ka.EfficientNetV2S,
260
+ ka.efficientnet_v2.preprocess_input,
261
+ (
262
+ "block6a_expand_conv", # 14x14
263
+ "block4a_expand_conv", # 28x28
264
+ "block2a_expand_activation", # 56x56
265
+ "stem_activation", # 112x112
266
+ ),
267
+ ),
253
268
  "efficientnetv2m": (
254
269
  ka.EfficientNetV2M,
255
270
  ka.efficientnet_v2.preprocess_input,
256
271
  (
257
272
  "block6a_expand_conv",
258
273
  "block4a_expand_conv",
259
- "block2e_add",
260
- "block1c_add",
274
+ "block2a_expand_activation",
275
+ "stem_activation",
276
+ ),
277
+ ),
278
+ "efficientnetv2l": (
279
+ ka.EfficientNetV2L,
280
+ ka.efficientnet_v2.preprocess_input,
281
+ (
282
+ "block6a_expand_conv",
283
+ "block4a_expand_conv",
284
+ "block2a_expand_activation",
285
+ "stem_activation",
261
286
  ),
262
287
  ),
263
288
  }
264
289
 
265
290
  @property
266
291
  def models(self):
292
+ """Get the dictionary of available backbones."""
267
293
  return self._models
268
294
 
269
295
  def models_names(self):
296
+ """Get the list of available backbone names."""
270
297
  return list(self.models.keys())
271
298
 
272
- def get_backbone(self, name, *args, **kwargs):
299
+ """Get a backbone model by name."""
300
+
301
+ def get_backbone(self, name, *args, **kwargs) -> keras.Model:
302
+ """Get a backbone model by name."""
273
303
  backbone = self._models.get(name)
274
304
  if backbone is None:
275
305
  raise ValueError(f"Backbone with name '{name}' is not supported.")
@@ -279,12 +309,15 @@ class BackbonesFactory:
279
309
  return model
280
310
 
281
311
  def get_feature_layers(self, name, n=5):
312
+ """Get the list of skip layers for a backbone by name."""
282
313
  return self._models.get(name)[2][:n]
283
314
 
284
315
  def get_preprocessing(self, name):
316
+ """Get the preprocessing function for a backbone by name."""
285
317
  return self._models.get(name)[1]
286
318
 
287
319
  def get_custom_objects(self, name):
320
+ """Get the custom objects for a backbone by name."""
288
321
  if name == "inceptionresnetv2":
289
322
  return irv2.get_custom_objects()
290
323
  return {}
@@ -38,8 +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 segmodels_keras._compat import load_weights
42
-
41
+ from ..utils import load_weights
43
42
  from .resnet_common import _obtain_input_shape
44
43
 
45
44
  BASE_WEIGHT_URL = (
@@ -315,16 +314,18 @@ def conv2d_bn(
315
314
 
316
315
 
317
316
  class CustomScaleLayer(layers.Layer):
318
- def __init__(self, scale, **kwargs):
317
+ """Custom layer to scale the residuals before adding them to the shortcut branch."""
318
+
319
+ def __init__(self, scale, **kwargs): # noqa: D107
319
320
  super().__init__(**kwargs)
320
321
  self.scale = scale
321
322
 
322
- def get_config(self):
323
+ def get_config(self): # noqa: D102
323
324
  config = super().get_config()
324
325
  config.update({"scale": self.scale})
325
326
  return config
326
327
 
327
- def call(self, inputs):
328
+ def call(self, inputs): # noqa: D102
328
329
  return inputs[0] + inputs[1] * self.scale
329
330
 
330
331
 
@@ -417,6 +418,9 @@ def preprocess_input(x, data_format=None):
417
418
 
418
419
  Args:
419
420
  x: a 4D numpy array consists of RGB values within [0, 255].
421
+ data_format: image data format, either "channels_first" or
422
+ "channels_last". Defaults to the value found in
423
+ `keras.backend.image_data_format()`.
420
424
 
421
425
  Returns:
422
426
  Preprocessed array.
@@ -444,4 +448,5 @@ def decode_predictions(preds, top=5):
444
448
 
445
449
 
446
450
  def get_custom_objects():
451
+ """Returns a dictionary containing the custom objects used by the model."""
447
452
  return {"CustomScaleLayer": CustomScaleLayer}
@@ -1,10 +1,13 @@
1
1
  """Inception V3 model for Keras.
2
+
2
3
  Note that the input image format for this model is different than for
3
4
  the VGG16 and ResNet models (299x299 instead of 224x224),
4
5
  and that the input preprocessing function is also different (same as Xception).
5
- # Reference
6
+
7
+ References:
8
+ ----------
6
9
  - [Rethinking the Inception Architecture for Computer Vision](
7
- http://arxiv.org/abs/1512.00567) (CVPR 2016)
10
+ http://arxiv.org/abs/1512.00567) (CVPR 2016).
8
11
  """
9
12
 
10
13
  import os
@@ -14,7 +17,7 @@ from keras import backend, layers, models
14
17
  from keras import utils as keras_utils
15
18
  from keras.applications import imagenet_utils
16
19
 
17
- from segmodels_keras._compat import load_weights
20
+ from ..utils import load_weights
18
21
 
19
22
  WEIGHTS_PATH = (
20
23
  "https://github.com/fchollet/deep-learning-models/"
@@ -30,6 +33,7 @@ WEIGHTS_PATH_NO_TOP = (
30
33
 
31
34
  def conv2d_bn(x, filters, num_row, num_col, padding="same", strides=(1, 1), name=None):
32
35
  """Utility function to apply conv + BN.
36
+
33
37
  # Arguments
34
38
  x: input tensor.
35
39
  filters: filters in `Conv2D`.
@@ -76,9 +80,11 @@ def InceptionV3(
76
80
  **kwargs, # noqa: ARG001
77
81
  ):
78
82
  """Instantiates the Inception v3 architecture.
83
+
79
84
  Optionally loads weights pre-trained on ImageNet.
80
85
  Note that the data format convention used by the model is
81
86
  the one specified in your Keras config at `~/.keras/keras.json`.
87
+
82
88
  # Arguments
83
89
  include_top: whether to include the fully-connected
84
90
  layer at the top of the network.
@@ -372,6 +378,7 @@ def InceptionV3(
372
378
 
373
379
  def preprocess_input(x, **kwargs):
374
380
  """Preprocesses a numpy array encoding a batch of images.
381
+
375
382
  # Arguments
376
383
  x: a 4D numpy array consists of RGB values within [0, 255].
377
384
  # Returns
@@ -1,4 +1,4 @@
1
- """ResNet18 and ResNet34 Keras models.
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
@@ -38,8 +38,7 @@ from keras import backend, layers, models
38
38
  from keras.applications import imagenet_utils
39
39
  from keras.utils import get_source_inputs
40
40
 
41
- from segmodels_keras._compat import load_weights
42
-
41
+ from ..utils import load_weights
43
42
  from .resnet_common import _obtain_input_shape
44
43
 
45
44
  momentum = 0.99
@@ -110,7 +109,6 @@ def ResNet(
110
109
  Returns:
111
110
  A ``keras.Model`` instance.
112
111
  """
113
-
114
112
  # Determine proper input shape
115
113
  input_shape = _obtain_input_shape(
116
114
  input_shape,
@@ -359,6 +357,9 @@ def preprocess_input(x, data_format=None):
359
357
 
360
358
  Args:
361
359
  x: a 4D numpy array consists of RGB values within [0, 255].
360
+ data_format: image data format, either "channels_first" or
361
+ "channels_last". Defaults to the value found in
362
+ `keras.backend.image_data_format()`.
362
363
 
363
364
  Returns:
364
365
  Preprocessed array.
@@ -1,3 +1,5 @@
1
+ """Base module for segmentation models in Keras."""
2
+
1
3
  from . import functional as functional
2
4
  from .objects import KerasObject as KerasObject
3
5
  from .objects import Loss as Loss
@@ -1,3 +1,7 @@
1
+ """Functional utilities for segmentation models."""
2
+
3
+ from typing import Any
4
+
1
5
  import keras
2
6
 
3
7
  from segmodels_keras._compat import KERAS_GTE_3
@@ -7,7 +11,7 @@ if KERAS_GTE_3:
7
11
  else:
8
12
  from keras import backend as ops
9
13
 
10
- SMOOTH = 1e-5
14
+ SMOOTH: float = 1e-5
11
15
 
12
16
 
13
17
  # ----------------------------------------------------------------
@@ -15,7 +19,7 @@ SMOOTH = 1e-5
15
19
  # ----------------------------------------------------------------
16
20
 
17
21
 
18
- def _gather_channels(x, indexes, **kwargs):
22
+ def _gather_channels(x: Any, indexes: Any, **kwargs: Any) -> Any:
19
23
  backend = kwargs["backend"]
20
24
 
21
25
  """Slice tensor along channels axis by given indexes"""
@@ -30,7 +34,8 @@ def _gather_channels(x, indexes, **kwargs):
30
34
  return x
31
35
 
32
36
 
33
- def get_reduce_axes(per_image, **kwargs):
37
+ def get_reduce_axes(per_image: bool, **kwargs: Any) -> list[int]:
38
+ """Get axes for reduction operations."""
34
39
  backend = kwargs["backend"]
35
40
 
36
41
  axes = [1, 2] if backend.image_data_format() == "channels_last" else [2, 3]
@@ -39,17 +44,20 @@ def get_reduce_axes(per_image, **kwargs):
39
44
  return axes
40
45
 
41
46
 
42
- def gather_channels(*xs, indexes=None, **kwargs):
43
- """Slice tensors along channels axis by given indexes"""
47
+ def gather_channels(
48
+ *xs: Any, indexes: int | list[int] | None = None, **kwargs: Any
49
+ ) -> tuple[Any, ...] | tuple[Any]:
50
+ """Slice tensors along channels axis by given indexes."""
44
51
  if indexes is None:
45
52
  return xs
46
53
  elif isinstance(indexes, (int)):
47
54
  indexes = [indexes]
48
- xs = [_gather_channels(x, indexes=indexes, **kwargs) for x in xs]
55
+ xs = tuple(_gather_channels(x, indexes=indexes, **kwargs) for x in xs)
49
56
  return xs
50
57
 
51
58
 
52
59
  def round_if_needed(x, threshold, **kwargs):
60
+ """Round predictions if threshold is not None."""
53
61
  if threshold is not None:
54
62
  x = ops.greater(x, threshold)
55
63
  if not KERAS_GTE_3:
@@ -61,6 +69,10 @@ def round_if_needed(x, threshold, **kwargs):
61
69
 
62
70
 
63
71
  def average(x, per_image=False, class_weights=None, **kwargs): # noqa: ARG001
72
+ """Calculate the average of a tensor.
73
+
74
+ Optionally per image and with class weights.
75
+ """
64
76
  if per_image:
65
77
  x = ops.mean(x, axis=0)
66
78
  if class_weights is not None:
@@ -83,12 +95,14 @@ def iou_score(
83
95
  threshold=None,
84
96
  **kwargs,
85
97
  ):
86
- r"""The `Jaccard index`_, also known as Intersection over Union and the Jaccard
87
- similarity coefficient (originally coined coefficient de communauté by Paul
88
- Jaccard), is a statistic used for comparing the similarity and diversity of sample
98
+ r"""The `Jaccard index`_.
99
+
100
+ Also known as Intersection over Union and the Jaccard similarity coefficient
101
+ (originally coined coefficient de communauté by Paul Jaccard),
102
+ is a statistic used for comparing the similarity and diversity of sample
89
103
  sets. The Jaccard coefficient measures similarity between finite sample sets,
90
104
  and is defined as the size of the intersection divided by the size of the union of
91
- the sample sets:
105
+ the sample sets.
92
106
 
93
107
  .. math:: J(A, B) = \frac{A \cap B}{A \cup B}
94
108
 
@@ -103,6 +117,8 @@ def iou_score(
103
117
  else over whole batch
104
118
  threshold: value to round predictions (use ``>`` comparison), if ``None``
105
119
  prediction will not be round
120
+ kwargs: additional arguments, for example, ``backend`` is required for some
121
+ operations
106
122
 
107
123
  Returns:
108
124
  IoU/Jaccard score in range [0, 1]
@@ -135,9 +151,10 @@ def f_score(
135
151
  threshold=None,
136
152
  **kwargs,
137
153
  ):
138
- r"""The F-score (Dice coefficient) can be interpreted as a weighted average of the
139
- precision and recall, where an F-score reaches its best value at 1 and worst score
140
- at 0.
154
+ r"""The F-score (Dice coefficient).
155
+
156
+ Can be interpreted as a weighted average of the precision and recall, where an
157
+ F-score reaches its best value at 1 and worst score at 0.
141
158
  The relative contribution of ``precision`` and ``recall`` to the F1-score are equal.
142
159
 
143
160
  The formula for the F score is:
@@ -167,6 +184,8 @@ def f_score(
167
184
  else over whole batch
168
185
  threshold: value to round predictions (use ``>`` comparison), if ``None``
169
186
  prediction will not be round
187
+ kwargs: additional arguments, for example, ``backend`` is required for some
188
+ operations
170
189
 
171
190
  Returns:
172
191
  F-score in range [0, 1]
@@ -220,6 +239,8 @@ def precision(
220
239
  threshold: Float value to round predictions (use ``>`` comparison), if ``None``
221
240
  prediction will not be round.
222
241
  name: Optional string, if ``None`` default ``precision`` name is used.
242
+ kwargs: additional arguments, for example, ``backend`` is required for some
243
+ operations
223
244
 
224
245
  Returns:
225
246
  float: precision score
@@ -269,6 +290,8 @@ def recall(
269
290
  threshold: Float value to round predictions (use ``>`` comparison), if ``None``
270
291
  prediction will not be round.
271
292
  name: Optional string, if ``None`` default ``precision`` name is used.
293
+ kwargs: additional arguments, for example, ``backend`` is required for some
294
+ operations
272
295
 
273
296
  Returns:
274
297
  float: recall score
@@ -292,6 +315,7 @@ def recall(
292
315
 
293
316
 
294
317
  def categorical_crossentropy(gt, pr, class_weights=1.0, class_indexes=None, **kwargs):
318
+ """Calculate categorical crossentropy loss between gt and pr tensors."""
295
319
  backend = kwargs["backend"]
296
320
 
297
321
  gt, pr = gather_channels(gt, pr, indexes=class_indexes, **kwargs)
@@ -309,11 +333,12 @@ def categorical_crossentropy(gt, pr, class_weights=1.0, class_indexes=None, **kw
309
333
 
310
334
 
311
335
  def binary_crossentropy(gt, pr, **kwargs): # noqa: ARG001
336
+ """Calculate binary crossentropy loss between gt and pr tensors."""
312
337
  return ops.mean(ops.binary_crossentropy(gt, pr))
313
338
 
314
339
 
315
340
  def categorical_focal_loss(gt, pr, gamma=2.0, alpha=0.25, class_indexes=None, **kwargs):
316
- r"""Implementation of Focal Loss from the paper in multiclass classification
341
+ r"""Implementation of Focal Loss from the paper in multiclass classification.
317
342
 
318
343
  Formula:
319
344
  loss = - gt * alpha * ((1 - pr)^gamma) * log(pr)
@@ -325,6 +350,8 @@ def categorical_focal_loss(gt, pr, gamma=2.0, alpha=0.25, class_indexes=None, **
325
350
  gamma: focusing parameter for modulating factor (1-p), default 2.0
326
351
  class_indexes: Optional integer or list of integers, classes to consider, if
327
352
  ``None`` all classes are used.
353
+ kwargs: additional arguments, for example, ``backend`` is required for some
354
+ operations
328
355
 
329
356
  """
330
357
  gt, pr = gather_channels(gt, pr, indexes=class_indexes, **kwargs)
@@ -339,7 +366,7 @@ def categorical_focal_loss(gt, pr, gamma=2.0, alpha=0.25, class_indexes=None, **
339
366
 
340
367
 
341
368
  def binary_focal_loss(gt, pr, gamma=2.0, alpha=0.25, **kwargs): # noqa: ARG001
342
- r"""Implementation of Focal Loss from the paper in binary classification
369
+ r"""Implementation of Focal Loss from the paper in binary classification.
343
370
 
344
371
  Formula:
345
372
  loss = - gt * alpha * ((1 - pr)^gamma) * log(pr) \
@@ -350,6 +377,8 @@ def binary_focal_loss(gt, pr, gamma=2.0, alpha=0.25, **kwargs): # noqa: ARG001
350
377
  pr: prediction 4D keras tensor (B, H, W, C) or (B, C, H, W)
351
378
  alpha: the same as weighting factor in balanced cross entropy, default 0.25
352
379
  gamma: focusing parameter for modulating factor (1-p), default 2.0
380
+ kwargs: additional arguments, for example, ``backend`` is required for some
381
+ operations
353
382
 
354
383
  """
355
384
  # clip to prevent NaN's and Inf's