segmodels-keras 0.1.0.dev7__tar.gz → 0.1.0.dev9__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 (42) hide show
  1. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/PKG-INFO +1 -1
  2. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/README.rst +3 -1
  3. segmodels_keras-0.1.0.dev9/segmodels_keras/_compat.py +123 -0
  4. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/_version.py +1 -1
  5. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/_weights.py +2 -1
  6. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/backbones_factory.py +14 -9
  7. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/inception_resnet_v2.py +6 -119
  8. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/inception_v3.py +4 -2
  9. segmodels_keras-0.1.0.dev9/segmodels_keras/backbones/resnet_18_34.py +366 -0
  10. segmodels_keras-0.1.0.dev9/segmodels_keras/backbones/resnet_common.py +122 -0
  11. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/fpn.py +3 -2
  12. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/linknet.py +3 -2
  13. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/pspnet.py +3 -2
  14. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/unet.py +4 -2
  15. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/PKG-INFO +1 -1
  16. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/SOURCES.txt +2 -1
  17. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/tests/test_utils.py +84 -0
  18. segmodels_keras-0.1.0.dev7/segmodels_keras/_compat.py +0 -4
  19. segmodels_keras-0.1.0.dev7/segmodels_keras/backbones/resnet.py +0 -494
  20. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/LICENSE +0 -0
  21. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/MANIFEST.in +0 -0
  22. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/pyproject.toml +0 -0
  23. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/__init__.py +0 -0
  24. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/__init__.py +0 -0
  25. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/backbones/_common_blocks.py +0 -0
  26. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/base/__init__.py +0 -0
  27. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/base/functional.py +0 -0
  28. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/base/objects.py +0 -0
  29. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/losses.py +0 -0
  30. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/metrics.py +0 -0
  31. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/__init__.py +0 -0
  32. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/_common_blocks.py +0 -0
  33. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/models/_utils.py +0 -0
  34. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras/utils.py +0 -0
  35. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/dependency_links.txt +0 -0
  36. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/requires.txt +0 -0
  37. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/segmodels_keras.egg-info/top_level.txt +0 -0
  38. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/setup.cfg +0 -0
  39. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/setup.py +0 -0
  40. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/tests/test_backbones.py +0 -0
  41. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/tests/test_metrics.py +0 -0
  42. {segmodels_keras-0.1.0.dev7 → segmodels_keras-0.1.0.dev9}/tests/test_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: segmodels_keras
3
- Version: 0.1.0.dev7
3
+ Version: 0.1.0.dev9
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
@@ -24,7 +24,9 @@ improvements and support for some extra backbone models.
24
24
  It is not meant as a full replacement of the original library, but rather as a
25
25
  solution for a library I developed and depended on segmentation_models:
26
26
  `orthoseg <https://github.com/orthoseg/orthoseg>`__ . Hence, full backwards
27
- compatibility,... or support for all features is not guaranteed.
27
+ compatibility or support for all features is not an explicit goal. For example,
28
+ the resnet backbones got small changes compared to the original library, so existing
29
+ weights you saved for them won't be compatible.
28
30
 
29
31
 
30
32
  **The main features** of this library are:
@@ -0,0 +1,123 @@
1
+ from collections import defaultdict
2
+ from pathlib import Path
3
+
4
+ import keras
5
+ from packaging.version import parse as parse_version
6
+
7
+ KERAS_GTE_3 = parse_version(keras.__version__) >= parse_version("3.0.0")
8
+
9
+
10
+ def _is_keras_v3_weights_hdf5(filepath):
11
+ try:
12
+ import h5py # noqa: PLC0415
13
+ except ImportError:
14
+ return False
15
+
16
+ try:
17
+ with h5py.File(filepath, "r") as handle:
18
+ return "layers" in handle and "vars" in handle
19
+ except OSError:
20
+ return False
21
+
22
+
23
+ def _get_sorted_hdf5_var_keys(vars_group):
24
+ return sorted(vars_group.keys(), key=int)
25
+
26
+
27
+ def _get_keras_v3_weighted_layers(handle):
28
+ import h5py # noqa: PLC0415
29
+
30
+ weighted_layers = []
31
+ for layer_name in handle["layers"].keys():
32
+ layer_group = handle["layers"][layer_name]
33
+ vars_group = layer_group.get("vars")
34
+ if vars_group is None or not vars_group.keys():
35
+ continue
36
+
37
+ signature = tuple(
38
+ tuple(vars_group[key].shape)
39
+ for key in _get_sorted_hdf5_var_keys(vars_group)
40
+ )
41
+ weighted_layers.append(
42
+ {
43
+ "name": layer_name,
44
+ "addr": h5py.h5o.get_info(layer_group.id).addr,
45
+ "signature": signature,
46
+ "vars_group": vars_group,
47
+ }
48
+ )
49
+
50
+ weighted_layers.sort(key=lambda layer: layer["addr"])
51
+ return weighted_layers
52
+
53
+
54
+ def _load_keras_v3_weights_hdf5(model, filepath):
55
+ import h5py # noqa: PLC0415
56
+
57
+ with h5py.File(filepath, "r") as handle:
58
+ saved_layers = _get_keras_v3_weighted_layers(handle)
59
+ saved_layers_by_signature = defaultdict(list)
60
+ for saved_layer in saved_layers:
61
+ saved_layers_by_signature[saved_layer["signature"]].append(saved_layer)
62
+
63
+ loaded_layers = 0
64
+
65
+ for layer in model.layers:
66
+ if not layer.weights:
67
+ continue
68
+
69
+ signature = tuple(tuple(variable.shape) for variable in layer.weights)
70
+ if not saved_layers_by_signature[signature]:
71
+ raise ValueError(
72
+ "Unable to locate matching Keras v3 HDF5 weights for layer "
73
+ f"'{layer.name}' with shapes {list(signature)}."
74
+ )
75
+
76
+ vars_group = saved_layers_by_signature[signature].pop(0)["vars_group"]
77
+ weight_values = [
78
+ vars_group[key][()] for key in _get_sorted_hdf5_var_keys(vars_group)
79
+ ]
80
+ layer.set_weights(weight_values)
81
+ loaded_layers += 1
82
+
83
+ if loaded_layers == 0:
84
+ raise ValueError(
85
+ "No matching weighted layers found in Keras v3 HDF5 weights file: "
86
+ f"{filepath}"
87
+ )
88
+
89
+
90
+ def load_weights(model, filepath):
91
+ """Load weights from an HDF5 file into a Keras model.
92
+
93
+ TensorFlow/Keras 2.10 and 2.11 can only read the legacy HDF5 format
94
+ (``layer_names`` attribute at the root). Keras 3 switched to a newer
95
+ layout where weights are stored under ``layers/<name>/vars/<index>``.
96
+ When a file saved with Keras 3 is opened by the TF 2.10/2.11 loader the
97
+ error "found 0 saved layers" is raised because the legacy reader finds no
98
+ ``layer_names`` attribute.
99
+
100
+ This wrapper calls ``model.load_weights`` first. If that raises the
101
+ "found 0 saved layers" ValueError *and* the file is detected as a Keras 3
102
+ weights HDF5 file, it falls back to a manual reader that matches each
103
+ model layer to its saved counterpart by weight-shape signature and file
104
+ creation order.
105
+
106
+ Args:
107
+ model: Keras model whose weights should be restored.
108
+ filepath (str | Path): Path to a ``.h5`` or ``.hdf5`` weights file.
109
+ """
110
+ try:
111
+ model.load_weights(filepath)
112
+ except ValueError as ex:
113
+ filepath = str(filepath)
114
+ message = str(ex)
115
+ is_hdf5 = Path(filepath).suffix in {".h5", ".hdf5"}
116
+ if (
117
+ not is_hdf5
118
+ or "found 0 saved layers" not in message
119
+ or not _is_keras_v3_weights_hdf5(filepath)
120
+ ):
121
+ raise
122
+
123
+ _load_keras_v3_weights_hdf5(model, filepath)
@@ -1,2 +1,2 @@
1
- __versioninfo__ = (0, 1, 0, "dev7")
1
+ __versioninfo__ = (0, 1, 0, "dev9")
2
2
  __version__ = ".".join(map(str, __versioninfo__))
@@ -3,6 +3,7 @@
3
3
  Source: https://github.com/qubvel/classification_models
4
4
  """
5
5
 
6
+ from segmodels_keras._compat import load_weights
6
7
  from keras.utils import get_file
7
8
 
8
9
  __all__ = ["load_model_weights"]
@@ -35,7 +36,7 @@ def load_model_weights(model, model_name, dataset, classes, include_top):
35
36
  md5_hash=weights["md5"],
36
37
  )
37
38
 
38
- model.load_weights(weights_path)
39
+ load_weights(model, weights_path)
39
40
 
40
41
  else:
41
42
  raise ValueError(
@@ -2,7 +2,7 @@ import keras.applications as ka
2
2
 
3
3
  from . import inception_resnet_v2 as irv2
4
4
  from . import inception_v3 as iv3
5
- from . import resnet
5
+ from . import resnet_18_34
6
6
 
7
7
 
8
8
  class BackbonesFactory:
@@ -17,17 +17,22 @@ class BackbonesFactory:
17
17
  """
18
18
 
19
19
  _models = { # noqa: RUF012
20
- # ResNets < 50 layers are NOT available via keras.applications, so use
21
- # implementation from classification_models.
20
+ # ResNets < 50 layers are NOT available via keras.applications, so use a
21
+ # torchvision-compatible post-activation BasicBlock implementation.
22
+ # Layer names follow the keras.applications ResNet50+ naming convention.
22
23
  "resnet18": (
23
- resnet.ResNet18,
24
- resnet.preprocess_input,
25
- ("stage4_unit1_relu1", "stage3_unit1_relu1", "stage2_unit1_relu1", "relu0"),
24
+ resnet_18_34.ResNet18,
25
+ resnet_18_34.preprocess_input,
26
+ # resnet18: layer1=2, layer2=2, layer3=2, layer4=2 blocks
27
+ # backbone output = conv5_block2_out (512ch); skips are the 4 stages before it
28
+ ("conv4_block2_out", "conv3_block2_out", "conv2_block2_out", "conv1_relu"),
26
29
  ),
27
30
  "resnet34": (
28
- resnet.ResNet34,
29
- resnet.preprocess_input,
30
- ("stage4_unit1_relu1", "stage3_unit1_relu1", "stage2_unit1_relu1", "relu0"),
31
+ resnet_18_34.ResNet34,
32
+ resnet_18_34.preprocess_input,
33
+ # resnet34: layer1=3, layer2=4, layer3=6, layer4=3 blocks
34
+ # backbone output = conv5_block3_out (512ch); skips are the 4 stages before it
35
+ ("conv4_block6_out", "conv3_block4_out", "conv2_block3_out", "conv1_relu"),
31
36
  ),
32
37
  # ResNets > 50 layers are available via keras.applications, so use those.
33
38
  # Skip layers (inverted) from https://github.com/yingkaisha/keras-unet-collection
@@ -32,13 +32,16 @@ are the following:
32
32
  keras, and here we need to use the public API of keras.
33
33
  """
34
34
 
35
- import warnings
36
35
  from pathlib import Path
37
36
 
38
37
  from keras import backend, layers, models
39
38
  from keras.applications import imagenet_utils
40
39
  from keras.utils import get_file, get_source_inputs
41
40
 
41
+ from segmodels_keras._compat import load_weights
42
+
43
+ from .resnet_common import _obtain_input_shape
44
+
42
45
  BASE_WEIGHT_URL = (
43
46
  "https://storage.googleapis.com/tensorflow/keras-applications/inception_resnet_v2/"
44
47
  )
@@ -260,9 +263,9 @@ def InceptionResNetV2(
260
263
  cache_subdir="models",
261
264
  file_hash="d19885ff4a710c122648d3b5c3b684e4",
262
265
  )
263
- model.load_weights(weights_path)
266
+ load_weights(model, weights_path)
264
267
  elif weights is not None:
265
- model.load_weights(weights)
268
+ load_weights(model, weights)
266
269
 
267
270
  return model
268
271
 
@@ -442,119 +445,3 @@ def decode_predictions(preds, top=5):
442
445
 
443
446
  def get_custom_objects():
444
447
  return {"CustomScaleLayer": CustomScaleLayer}
445
-
446
-
447
- def _obtain_input_shape(
448
- input_shape,
449
- default_size,
450
- min_size,
451
- data_format,
452
- require_flatten,
453
- weights=None,
454
- ):
455
- """Internal utility to compute/validate a model's input shape.
456
-
457
- Args:
458
- input_shape: Either None (will return the default network input shape),
459
- or a user-provided shape to be validated.
460
- default_size: Default input width/height for the model.
461
- min_size: Minimum input width/height accepted by the model.
462
- data_format: Image data format to use.
463
- require_flatten: Whether the model is expected to
464
- be linked to a classifier via a Flatten layer.
465
- weights: One of `None` (random initialization)
466
- or 'imagenet' (pre-training on ImageNet).
467
- If weights='imagenet' input channels must be equal to 3.
468
-
469
- Returns:
470
- An integer shape tuple (may include None entries).
471
-
472
- Raises:
473
- ValueError: In case of invalid argument values.
474
- """
475
- if weights != "imagenet" and input_shape and len(input_shape) == 3:
476
- if data_format == "channels_first":
477
- correct_channel_axis = 1 if len(input_shape) == 4 else 0
478
- if input_shape[correct_channel_axis] not in {1, 3}:
479
- warnings.warn(
480
- "This model usually expects 1 or 3 input channels. "
481
- "However, it was passed an input_shape "
482
- f"with {input_shape[0]} input channels.",
483
- stacklevel=2,
484
- )
485
- default_shape = (input_shape[0], default_size, default_size)
486
- else:
487
- if input_shape[-1] not in {1, 3}:
488
- warnings.warn(
489
- "This model usually expects 1 or 3 input channels. "
490
- "However, it was passed an input_shape "
491
- f"with {input_shape[-1]} input channels.",
492
- stacklevel=2,
493
- )
494
- default_shape = (default_size, default_size, input_shape[-1])
495
- else:
496
- if data_format == "channels_first":
497
- default_shape = (3, default_size, default_size)
498
- else:
499
- default_shape = (default_size, default_size, 3)
500
- if weights == "imagenet" and require_flatten:
501
- if input_shape is not None:
502
- if input_shape != default_shape:
503
- raise ValueError(
504
- "When setting `include_top=True` "
505
- "and loading `imagenet` weights, "
506
- f"`input_shape` should be {default_shape}. "
507
- f"Received: input_shape={input_shape}"
508
- )
509
- return default_shape
510
- if input_shape:
511
- if data_format == "channels_first":
512
- if input_shape is not None:
513
- if len(input_shape) != 3:
514
- raise ValueError("`input_shape` must be a tuple of three integers.")
515
- if input_shape[0] != 3 and weights == "imagenet":
516
- raise ValueError(
517
- "The input must have 3 channels; Received "
518
- f"`input_shape={input_shape}`"
519
- )
520
- if (input_shape[1] is not None and input_shape[1] < min_size) or (
521
- input_shape[2] is not None and input_shape[2] < min_size
522
- ):
523
- raise ValueError(
524
- f"Input size must be at least {min_size}"
525
- f"x{min_size}; Received: "
526
- f"input_shape={input_shape}"
527
- )
528
- else:
529
- if input_shape is not None:
530
- if len(input_shape) != 3:
531
- raise ValueError("`input_shape` must be a tuple of three integers.")
532
- if input_shape[-1] != 3 and weights == "imagenet":
533
- raise ValueError(
534
- "The input must have 3 channels; Received "
535
- f"`input_shape={input_shape}`"
536
- )
537
- if (input_shape[0] is not None and input_shape[0] < min_size) or (
538
- input_shape[1] is not None and input_shape[1] < min_size
539
- ):
540
- raise ValueError(
541
- "Input size must be at least "
542
- f"{min_size}x{min_size}; Received: "
543
- f"input_shape={input_shape}"
544
- )
545
- else:
546
- if require_flatten:
547
- input_shape = default_shape
548
- else:
549
- if data_format == "channels_first":
550
- input_shape = (3, None, None)
551
- else:
552
- input_shape = (None, None, 3)
553
- if require_flatten:
554
- if None in input_shape:
555
- raise ValueError(
556
- "If `include_top` is True, "
557
- "you should specify a static `input_shape`. "
558
- f"Received: input_shape={input_shape}"
559
- )
560
- return input_shape
@@ -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 segmodels_keras._compat 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
- model.load_weights(weights_path)
366
+ load_weights(model, weights_path)
365
367
  elif weights is not None:
366
- model.load_weights(weights)
368
+ load_weights(model, weights)
367
369
 
368
370
  return model
369
371