segmodels-keras 0.1.0.dev10__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 (45) hide show
  1. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/PKG-INFO +1 -1
  2. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/pyproject.toml +1 -1
  3. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/__init__.py +5 -2
  4. segmodels_keras-0.1.0.dev11/segmodels_keras/_version.py +2 -0
  5. segmodels_keras-0.1.0.dev11/segmodels_keras/backbones/__init__.py +1 -0
  6. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/_common_blocks.py +6 -3
  7. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/backbones_factory.py +37 -4
  8. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/inception_resnet_v2.py +9 -3
  9. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/inception_v3.py +9 -2
  10. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/resnet_18_34.py +3 -1
  11. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/base/__init__.py +2 -0
  12. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/base/functional.py +35 -10
  13. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/base/objects.py +31 -18
  14. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/losses.py +24 -22
  15. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/metrics.py +22 -19
  16. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/_common_blocks.py +1 -2
  17. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/_utils.py +1 -1
  18. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/fpn.py +26 -22
  19. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/linknet.py +27 -23
  20. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/pspnet.py +23 -22
  21. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/unet.py +38 -35
  22. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/utils.py +16 -13
  23. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras.egg-info/PKG-INFO +1 -1
  24. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/setup.py +2 -0
  25. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/tests/test_backbones.py +2 -0
  26. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/tests/test_helper.py +2 -0
  27. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/tests/test_metrics.py +2 -0
  28. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/tests/test_models.py +3 -1
  29. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/tests/test_utils.py +2 -0
  30. segmodels_keras-0.1.0.dev10/segmodels_keras/_version.py +0 -2
  31. segmodels_keras-0.1.0.dev10/segmodels_keras/backbones/__init__.py +0 -0
  32. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/LICENSE +0 -0
  33. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/MANIFEST.in +0 -0
  34. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/README.rst +0 -0
  35. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/_compat.py +0 -0
  36. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/_weights.py +0 -0
  37. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/backbones/resnet_common.py +0 -0
  38. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/__init__.py +0 -0
  39. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/models/model_factory.py +0 -0
  40. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras/py.typed +0 -0
  41. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras.egg-info/SOURCES.txt +0 -0
  42. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras.egg-info/dependency_links.txt +0 -0
  43. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras.egg-info/requires.txt +0 -0
  44. {segmodels_keras-0.1.0.dev10 → segmodels_keras-0.1.0.dev11}/segmodels_keras.egg-info/top_level.txt +0 -0
  45. {segmodels_keras-0.1.0.dev10 → 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.dev10
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
@@ -37,7 +37,7 @@ select = [
37
37
  "NPY", # NumPy-specific rules
38
38
  "E", # pycodestyle errors
39
39
  "W", # pycodestyle warnings
40
- # "D", # pydocstyle
40
+ "D", # pydocstyle
41
41
  "F", # pyflakes
42
42
  "PLC", # pylint convention
43
43
  "PLE", # pylint error
@@ -1,3 +1,5 @@
1
+ """Segmentation Models for Keras."""
2
+
1
3
  import functools # noqa: I001
2
4
  import os
3
5
  from typing import Any
@@ -43,12 +45,12 @@ def filter_kwargs(func: Callable[..., Any]) -> Callable[..., Any]:
43
45
 
44
46
 
45
47
  def framework() -> str | None:
46
- """Return name of Segmentation Models framework"""
48
+ """Return name of Segmentation Models framework."""
47
49
  return _KERAS_FRAMEWORK
48
50
 
49
51
 
50
52
  def set_framework(name: str) -> None:
51
- """Set framework for Segmentation Models
53
+ """Set framework for Segmentation Models.
52
54
 
53
55
  Args:
54
56
  name (str): one of ``keras``, ``tf.keras``, case insensitive.
@@ -118,6 +120,7 @@ get_available_backbone_names = Backbones.models_names
118
120
 
119
121
 
120
122
  def get_preprocessing(name: str) -> Callable[[Any], Any]:
123
+ """Get the preprocessing function for a backbone by name."""
121
124
  return Backbones.get_preprocessing(name)
122
125
 
123
126
 
@@ -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
@@ -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
@@ -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 {}
@@ -314,16 +314,18 @@ def conv2d_bn(
314
314
 
315
315
 
316
316
  class CustomScaleLayer(layers.Layer):
317
- 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
318
320
  super().__init__(**kwargs)
319
321
  self.scale = scale
320
322
 
321
- def get_config(self):
323
+ def get_config(self): # noqa: D102
322
324
  config = super().get_config()
323
325
  config.update({"scale": self.scale})
324
326
  return config
325
327
 
326
- def call(self, inputs):
328
+ def call(self, inputs): # noqa: D102
327
329
  return inputs[0] + inputs[1] * self.scale
328
330
 
329
331
 
@@ -416,6 +418,9 @@ def preprocess_input(x, data_format=None):
416
418
 
417
419
  Args:
418
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()`.
419
424
 
420
425
  Returns:
421
426
  Preprocessed array.
@@ -443,4 +448,5 @@ def decode_predictions(preds, top=5):
443
448
 
444
449
 
445
450
  def get_custom_objects():
451
+ """Returns a dictionary containing the custom objects used by the model."""
446
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
@@ -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
@@ -109,7 +109,6 @@ def ResNet(
109
109
  Returns:
110
110
  A ``keras.Model`` instance.
111
111
  """
112
-
113
112
  # Determine proper input shape
114
113
  input_shape = _obtain_input_shape(
115
114
  input_shape,
@@ -358,6 +357,9 @@ def preprocess_input(x, data_format=None):
358
357
 
359
358
  Args:
360
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()`.
361
363
 
362
364
  Returns:
363
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,5 @@
1
+ """Functional utilities for segmentation models."""
2
+
1
3
  from typing import Any
2
4
 
3
5
  import keras
@@ -33,6 +35,7 @@ def _gather_channels(x: Any, indexes: Any, **kwargs: Any) -> Any:
33
35
 
34
36
 
35
37
  def get_reduce_axes(per_image: bool, **kwargs: Any) -> list[int]:
38
+ """Get axes for reduction operations."""
36
39
  backend = kwargs["backend"]
37
40
 
38
41
  axes = [1, 2] if backend.image_data_format() == "channels_last" else [2, 3]
@@ -44,7 +47,7 @@ def get_reduce_axes(per_image: bool, **kwargs: Any) -> list[int]:
44
47
  def gather_channels(
45
48
  *xs: Any, indexes: int | list[int] | None = None, **kwargs: Any
46
49
  ) -> tuple[Any, ...] | tuple[Any]:
47
- """Slice tensors along channels axis by given indexes"""
50
+ """Slice tensors along channels axis by given indexes."""
48
51
  if indexes is None:
49
52
  return xs
50
53
  elif isinstance(indexes, (int)):
@@ -54,6 +57,7 @@ def gather_channels(
54
57
 
55
58
 
56
59
  def round_if_needed(x, threshold, **kwargs):
60
+ """Round predictions if threshold is not None."""
57
61
  if threshold is not None:
58
62
  x = ops.greater(x, threshold)
59
63
  if not KERAS_GTE_3:
@@ -65,6 +69,10 @@ def round_if_needed(x, threshold, **kwargs):
65
69
 
66
70
 
67
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
+ """
68
76
  if per_image:
69
77
  x = ops.mean(x, axis=0)
70
78
  if class_weights is not None:
@@ -87,12 +95,14 @@ def iou_score(
87
95
  threshold=None,
88
96
  **kwargs,
89
97
  ):
90
- r"""The `Jaccard index`_, also known as Intersection over Union and the Jaccard
91
- similarity coefficient (originally coined coefficient de communauté by Paul
92
- 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
93
103
  sets. The Jaccard coefficient measures similarity between finite sample sets,
94
104
  and is defined as the size of the intersection divided by the size of the union of
95
- the sample sets:
105
+ the sample sets.
96
106
 
97
107
  .. math:: J(A, B) = \frac{A \cap B}{A \cup B}
98
108
 
@@ -107,6 +117,8 @@ def iou_score(
107
117
  else over whole batch
108
118
  threshold: value to round predictions (use ``>`` comparison), if ``None``
109
119
  prediction will not be round
120
+ kwargs: additional arguments, for example, ``backend`` is required for some
121
+ operations
110
122
 
111
123
  Returns:
112
124
  IoU/Jaccard score in range [0, 1]
@@ -139,9 +151,10 @@ def f_score(
139
151
  threshold=None,
140
152
  **kwargs,
141
153
  ):
142
- r"""The F-score (Dice coefficient) can be interpreted as a weighted average of the
143
- precision and recall, where an F-score reaches its best value at 1 and worst score
144
- 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.
145
158
  The relative contribution of ``precision`` and ``recall`` to the F1-score are equal.
146
159
 
147
160
  The formula for the F score is:
@@ -171,6 +184,8 @@ def f_score(
171
184
  else over whole batch
172
185
  threshold: value to round predictions (use ``>`` comparison), if ``None``
173
186
  prediction will not be round
187
+ kwargs: additional arguments, for example, ``backend`` is required for some
188
+ operations
174
189
 
175
190
  Returns:
176
191
  F-score in range [0, 1]
@@ -224,6 +239,8 @@ def precision(
224
239
  threshold: Float value to round predictions (use ``>`` comparison), if ``None``
225
240
  prediction will not be round.
226
241
  name: Optional string, if ``None`` default ``precision`` name is used.
242
+ kwargs: additional arguments, for example, ``backend`` is required for some
243
+ operations
227
244
 
228
245
  Returns:
229
246
  float: precision score
@@ -273,6 +290,8 @@ def recall(
273
290
  threshold: Float value to round predictions (use ``>`` comparison), if ``None``
274
291
  prediction will not be round.
275
292
  name: Optional string, if ``None`` default ``precision`` name is used.
293
+ kwargs: additional arguments, for example, ``backend`` is required for some
294
+ operations
276
295
 
277
296
  Returns:
278
297
  float: recall score
@@ -296,6 +315,7 @@ def recall(
296
315
 
297
316
 
298
317
  def categorical_crossentropy(gt, pr, class_weights=1.0, class_indexes=None, **kwargs):
318
+ """Calculate categorical crossentropy loss between gt and pr tensors."""
299
319
  backend = kwargs["backend"]
300
320
 
301
321
  gt, pr = gather_channels(gt, pr, indexes=class_indexes, **kwargs)
@@ -313,11 +333,12 @@ def categorical_crossentropy(gt, pr, class_weights=1.0, class_indexes=None, **kw
313
333
 
314
334
 
315
335
  def binary_crossentropy(gt, pr, **kwargs): # noqa: ARG001
336
+ """Calculate binary crossentropy loss between gt and pr tensors."""
316
337
  return ops.mean(ops.binary_crossentropy(gt, pr))
317
338
 
318
339
 
319
340
  def categorical_focal_loss(gt, pr, gamma=2.0, alpha=0.25, class_indexes=None, **kwargs):
320
- r"""Implementation of Focal Loss from the paper in multiclass classification
341
+ r"""Implementation of Focal Loss from the paper in multiclass classification.
321
342
 
322
343
  Formula:
323
344
  loss = - gt * alpha * ((1 - pr)^gamma) * log(pr)
@@ -329,6 +350,8 @@ def categorical_focal_loss(gt, pr, gamma=2.0, alpha=0.25, class_indexes=None, **
329
350
  gamma: focusing parameter for modulating factor (1-p), default 2.0
330
351
  class_indexes: Optional integer or list of integers, classes to consider, if
331
352
  ``None`` all classes are used.
353
+ kwargs: additional arguments, for example, ``backend`` is required for some
354
+ operations
332
355
 
333
356
  """
334
357
  gt, pr = gather_channels(gt, pr, indexes=class_indexes, **kwargs)
@@ -343,7 +366,7 @@ def categorical_focal_loss(gt, pr, gamma=2.0, alpha=0.25, class_indexes=None, **
343
366
 
344
367
 
345
368
  def binary_focal_loss(gt, pr, gamma=2.0, alpha=0.25, **kwargs): # noqa: ARG001
346
- r"""Implementation of Focal Loss from the paper in binary classification
369
+ r"""Implementation of Focal Loss from the paper in binary classification.
347
370
 
348
371
  Formula:
349
372
  loss = - gt * alpha * ((1 - pr)^gamma) * log(pr) \
@@ -354,6 +377,8 @@ def binary_focal_loss(gt, pr, gamma=2.0, alpha=0.25, **kwargs): # noqa: ARG001
354
377
  pr: prediction 4D keras tensor (B, H, W, C) or (B, C, H, W)
355
378
  alpha: the same as weighting factor in balanced cross entropy, default 0.25
356
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
357
382
 
358
383
  """
359
384
  # clip to prevent NaN's and Inf's
@@ -1,13 +1,17 @@
1
+ """Base objects for segmodels_keras."""
2
+
1
3
  from typing import Any
2
4
 
3
5
 
4
6
  class KerasObject:
7
+ """Base class for all objects in segmodels_keras."""
8
+
5
9
  _backend: Any = None
6
10
  _models: Any = None
7
11
  _layers: Any = None
8
12
  _utils: Any = None
9
13
 
10
- def __init__(self, name: str | None = None) -> None:
14
+ def __init__(self, name: str | None = None) -> None: # noqa: D107
11
15
  if (
12
16
  self.backend is None
13
17
  or self.utils is None
@@ -19,13 +23,13 @@ class KerasObject:
19
23
  self._name = name
20
24
 
21
25
  @property
22
- def __name__(self) -> str:
26
+ def __name__(self) -> str: # noqa: D105
23
27
  if self._name is None:
24
28
  return self.__class__.__name__ # type: ignore[return-value]
25
29
  return self._name
26
30
 
27
31
  @property
28
- def name(self) -> str:
32
+ def name(self) -> str: # noqa: D102
29
33
  return self.__name__
30
34
 
31
35
  @name.setter
@@ -33,14 +37,14 @@ class KerasObject:
33
37
  self._name = name
34
38
 
35
39
  @classmethod
36
- def set_submodules(cls, backend: Any, layers: Any, models: Any, utils: Any) -> None:
40
+ def set_submodules(cls, backend: Any, layers: Any, models: Any, utils: Any) -> None: # noqa: D102
37
41
  cls._backend = backend
38
42
  cls._layers = layers
39
43
  cls._models = models
40
44
  cls._utils = utils
41
45
 
42
46
  @property
43
- def submodules(self) -> dict[str, Any]:
47
+ def submodules(self) -> dict[str, Any]: # noqa: D102
44
48
  return {
45
49
  "backend": self.backend,
46
50
  "layers": self.layers,
@@ -49,48 +53,52 @@ class KerasObject:
49
53
  }
50
54
 
51
55
  @property
52
- def backend(self) -> Any:
56
+ def backend(self) -> Any: # noqa: D102
53
57
  return self._backend
54
58
 
55
59
  @property
56
- def layers(self) -> Any:
60
+ def layers(self) -> Any: # noqa: D102
57
61
  return self._layers
58
62
 
59
63
  @property
60
- def models(self) -> Any:
64
+ def models(self) -> Any: # noqa: D102
61
65
  return self._models
62
66
 
63
67
  @property
64
- def utils(self) -> Any:
68
+ def utils(self) -> Any: # noqa: D102
65
69
  return self._utils
66
70
 
67
71
 
68
72
  class Metric(KerasObject):
69
- pass
73
+ """Base class for all metrics."""
70
74
 
71
75
 
72
76
  class Loss(KerasObject):
73
- def __add__(self, other: Any) -> Any:
77
+ """Base class for all loss functions."""
78
+
79
+ def __add__(self, other: Any) -> Any: # noqa: D105
74
80
  if isinstance(other, Loss):
75
81
  return SumOfLosses(self, other)
76
82
  else:
77
83
  raise ValueError("Loss should be inherited from `Loss` class")
78
84
 
79
- def __radd__(self, other: Any) -> Any:
85
+ def __radd__(self, other: Any) -> Any: # noqa: D105
80
86
  return self.__add__(other)
81
87
 
82
- def __mul__(self, value: int | float) -> Any:
88
+ def __mul__(self, value: int | float) -> Any: # noqa: D105
83
89
  if isinstance(value, (int, float)):
84
90
  return MultipliedLoss(self, value)
85
91
  else:
86
92
  raise ValueError("Loss should be inherited from `BaseLoss` class")
87
93
 
88
- def __rmul__(self, other: Any) -> Any:
94
+ def __rmul__(self, other: Any) -> Any: # noqa: D105
89
95
  return self.__mul__(other)
90
96
 
91
97
 
92
98
  class MultipliedLoss(Loss):
93
- def __init__(self, loss: Any, multiplier: int | float) -> None:
99
+ """Class for multiplying loss by a constant value."""
100
+
101
+ def __init__(self, loss: Any, multiplier: int | float) -> None: # noqa: D107
94
102
 
95
103
  # resolve name
96
104
  if len(loss.__name__.split("+")) > 1:
@@ -101,16 +109,21 @@ class MultipliedLoss(Loss):
101
109
  self.loss = loss
102
110
  self.multiplier = multiplier
103
111
 
104
- def __call__(self, gt: Any, pr: Any) -> Any:
112
+ def __call__(self, gt: Any, pr: Any) -> Any: # noqa: D102
105
113
  return self.multiplier * self.loss(gt, pr)
106
114
 
107
115
 
108
116
  class SumOfLosses(Loss):
109
- def __init__(self, l1: Any, l2: Any) -> None:
117
+ """Class for summing two losses.
118
+
119
+ For example, you can sum DiceLoss and BinaryCELoss.
120
+ """
121
+
122
+ def __init__(self, l1: Any, l2: Any) -> None: # noqa: D107
110
123
  name = f"{l1.__name__}_plus_{l2.__name__}"
111
124
  super().__init__(name=name)
112
125
  self.l1 = l1
113
126
  self.l2 = l2
114
127
 
115
- def __call__(self, gt: Any, pr: Any) -> Any:
128
+ def __call__(self, gt: Any, pr: Any) -> Any: # noqa: D102
116
129
  return self.l1(gt, pr) + self.l2(gt, pr)