segmodels-keras 0.1.0.dev4__tar.gz → 0.1.0.dev5__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 (36) hide show
  1. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/PKG-INFO +1 -1
  2. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/README.rst +4 -1
  3. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/_version.py +1 -1
  4. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/models/fpn.py +12 -1
  5. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/models/linknet.py +12 -1
  6. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/models/pspnet.py +12 -1
  7. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/models/unet.py +12 -1
  8. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/utils.py +32 -0
  9. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras.egg-info/PKG-INFO +1 -1
  10. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/tests/test_utils.py +40 -0
  11. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/LICENSE +0 -0
  12. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/MANIFEST.in +0 -0
  13. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/pyproject.toml +0 -0
  14. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/__init__.py +0 -0
  15. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/_compat.py +0 -0
  16. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/backbones/__init__.py +0 -0
  17. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/backbones/backbones_factory.py +0 -0
  18. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/backbones/inception_resnet_v2.py +0 -0
  19. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/backbones/inception_v3.py +0 -0
  20. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/base/__init__.py +0 -0
  21. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/base/functional.py +0 -0
  22. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/base/objects.py +0 -0
  23. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/losses.py +0 -0
  24. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/metrics.py +0 -0
  25. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/models/__init__.py +0 -0
  26. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/models/_common_blocks.py +0 -0
  27. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras/models/_utils.py +0 -0
  28. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras.egg-info/SOURCES.txt +0 -0
  29. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras.egg-info/dependency_links.txt +0 -0
  30. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras.egg-info/requires.txt +0 -0
  31. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/segmodels_keras.egg-info/top_level.txt +0 -0
  32. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/setup.cfg +0 -0
  33. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/setup.py +0 -0
  34. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/tests/test_backbones.py +0 -0
  35. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/tests/test_metrics.py +0 -0
  36. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev5}/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.dev4
3
+ Version: 0.1.0.dev5
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,8 +1,11 @@
1
- |Actions Status| |PyPI version|
1
+ |Actions Status| |Code coverage| |PyPI version|
2
2
 
3
3
  .. |Actions Status| image:: https://github.com/orthoseg/segmodels_keras/actions/workflows/tests.yml/badge.svg?branch=main
4
4
  :target: https://github.com/orthoseg/segmodels_keras/actions/workflows/tests.yml?query=workflow%3ATests
5
5
 
6
+ .. |Code coverage| image:: https://codecov.io/gh/orthoseg/segmodels_keras/graph/badge.svg?token=o01OyTMDuj
7
+ :target: https://codecov.io/gh/orthoseg/segmodels_keras
8
+
6
9
  .. |PyPI version| image:: https://img.shields.io/pypi/v/segmodels-keras.svg
7
10
  :target: https://pypi.org/project/segmodels-keras
8
11
 
@@ -1,2 +1,2 @@
1
- __versioninfo__ = (0, 1, 0, "dev4")
1
+ __versioninfo__ = (0, 1, 0, "dev5")
2
2
  __version__ = ".".join(map(str, __versioninfo__))
@@ -107,6 +107,7 @@ def build_fpn(
107
107
  use_batchnorm=True,
108
108
  aggregation="sum",
109
109
  dropout=None,
110
+ weights_notop=None,
110
111
  ):
111
112
  input_ = backbone.input
112
113
  x = backbone.output
@@ -172,6 +173,12 @@ def build_fpn(
172
173
  size=(2, 2), interpolation="bilinear", name="final_upsampling"
173
174
  )(x)
174
175
 
176
+ # load weights without top if path provided
177
+ if weights_notop is not None:
178
+ model_notop = models.Model(input_, x)
179
+ model_notop.load_weights(weights_notop)
180
+ x = model_notop.output
181
+
175
182
  # model head (define number of output classes)
176
183
  x = layers.Conv2D(
177
184
  filters=classes,
@@ -200,6 +207,7 @@ def FPN(
200
207
  classes=21,
201
208
  activation="softmax",
202
209
  weights=None,
210
+ weights_notop=None,
203
211
  encoder_weights="imagenet",
204
212
  encoder_freeze=False,
205
213
  encoder_features="default",
@@ -219,7 +227,9 @@ def FPN(
219
227
  your model be able to process images af any size, but ``H`` and ``W`` of
220
228
  input images should be divisible by factor ``32``.
221
229
  classes: a number of classes for output (output shape - ``(h, w, classes)``).
222
- weights: optional, path to model weights.
230
+ weights: optional, path to model weights to be loaded.
231
+ weights_notop: optional, path to model weights without top (without segmentation
232
+ head) to be loaded.
223
233
  activation: name of one of ``keras.activations`` for last model layer
224
234
  (e.g. ``sigmoid``, ``softmax``, ``linear``).
225
235
  encoder_weights: one of ``None`` (random initialization), ``imagenet``
@@ -265,6 +275,7 @@ def FPN(
265
275
  activation=activation,
266
276
  classes=classes,
267
277
  aggregation=pyramid_aggregation,
278
+ weights_notop=weights_notop,
268
279
  )
269
280
 
270
281
  # lock encoder weights for fine-tuning
@@ -141,6 +141,7 @@ def build_linknet(
141
141
  classes=1,
142
142
  activation="sigmoid",
143
143
  use_batchnorm=True,
144
+ weights_notop=None,
144
145
  ):
145
146
  input_ = backbone.input
146
147
  x = backbone.output
@@ -169,6 +170,12 @@ def build_linknet(
169
170
  x, skip
170
171
  )
171
172
 
173
+ # load weights without top if path provided
174
+ if weights_notop is not None:
175
+ model_notop = models.Model(input_, x)
176
+ model_notop.load_weights(weights_notop)
177
+ x = model_notop.output
178
+
172
179
  # model head (define number of output classes)
173
180
  x = layers.Conv2D(
174
181
  filters=classes,
@@ -196,6 +203,7 @@ def Linknet(
196
203
  classes=1,
197
204
  activation="sigmoid",
198
205
  weights=None,
206
+ weights_notop=None,
199
207
  encoder_weights="imagenet",
200
208
  encoder_freeze=False,
201
209
  encoder_features="default",
@@ -219,7 +227,9 @@ def Linknet(
219
227
  classes: a number of classes for output (output shape - ``(h, w, classes)``).
220
228
  activation: name of one of ``keras.activations`` for last model layer
221
229
  (e.g. ``sigmoid``, ``softmax``, ``linear``).
222
- weights: optional, path to model weights.
230
+ weights: optional, path to model weights to be loaded.
231
+ weights_notop: optional, path to model weights without top (without segmentation
232
+ head) to be loaded.
223
233
  encoder_weights: one of ``None`` (random initialization), ``imagenet``
224
234
  (pre-training on ImageNet).
225
235
  encoder_freeze: if ``True`` set all layers of encoder (backbone model) as
@@ -274,6 +284,7 @@ def Linknet(
274
284
  activation=activation,
275
285
  n_upsample_blocks=len(decoder_filters),
276
286
  use_batchnorm=decoder_use_batchnorm,
287
+ weights_notop=weights_notop,
277
288
  )
278
289
 
279
290
  # lock encoder weights for fine-tuning
@@ -124,6 +124,7 @@ def build_psp(
124
124
  classes=21,
125
125
  activation="softmax",
126
126
  dropout=None,
127
+ weights_notop=None,
127
128
  ):
128
129
  input_ = backbone.input
129
130
  x = (
@@ -147,6 +148,12 @@ def build_psp(
147
148
  if dropout is not None:
148
149
  x = layers.SpatialDropout2D(dropout, name="spatial_dropout")(x)
149
150
 
151
+ # load weights without top if path provided
152
+ if weights_notop is not None:
153
+ model_notop = models.Model(input_, x)
154
+ model_notop.load_weights(weights_notop)
155
+ x = model_notop.output
156
+
150
157
  # model head
151
158
  x = layers.Conv2D(
152
159
  filters=classes,
@@ -177,6 +184,7 @@ def PSPNet(
177
184
  classes=21,
178
185
  activation="softmax",
179
186
  weights=None,
187
+ weights_notop=None,
180
188
  encoder_weights="imagenet",
181
189
  encoder_freeze=False,
182
190
  downsample_factor=8,
@@ -197,7 +205,9 @@ def PSPNet(
197
205
  classes: a number of classes for output (output shape - ``(h, w, classes)``).
198
206
  activation: name of one of ``keras.activations`` for last model layer
199
207
  (e.g. ``sigmoid``, ``softmax``, ``linear``).
200
- weights: optional, path to model weights.
208
+ weights: optional, path to model weights to be loaded.
209
+ weights_notop: optional, path to model weights without top (without segmentation
210
+ head) to be loaded.
201
211
  encoder_weights: one of ``None`` (random initialization), ``imagenet``
202
212
  (pre-training on ImageNet).
203
213
  encoder_freeze: if ``True`` set all layers of encoder (backbone model) as
@@ -250,6 +260,7 @@ def PSPNet(
250
260
  classes=classes,
251
261
  activation=activation,
252
262
  dropout=psp_dropout,
263
+ weights_notop=weights_notop,
253
264
  )
254
265
 
255
266
  # lock encoder weights for fine-tuning
@@ -113,6 +113,7 @@ def build_unet(
113
113
  classes=1,
114
114
  activation="sigmoid",
115
115
  use_batchnorm=True,
116
+ weights_notop=None,
116
117
  ):
117
118
  input_ = backbone.input
118
119
  x = backbone.output
@@ -141,6 +142,12 @@ def build_unet(
141
142
  x, skip
142
143
  )
143
144
 
145
+ # load weights without top if path provided
146
+ if weights_notop is not None:
147
+ model_notop = models.Model(input_, x)
148
+ model_notop.load_weights(weights_notop)
149
+ x = model_notop.output
150
+
144
151
  # model head (define number of output classes)
145
152
  x = layers.Conv2D(
146
153
  filters=classes,
@@ -169,6 +176,7 @@ def Unet(
169
176
  classes=1,
170
177
  activation="sigmoid",
171
178
  weights=None,
179
+ weights_notop=None,
172
180
  encoder_weights="imagenet",
173
181
  encoder_freeze=False,
174
182
  encoder_features="default",
@@ -190,7 +198,9 @@ def Unet(
190
198
  classes: a number of classes for output (output shape - ``(h, w, classes)``).
191
199
  activation: name of one of ``keras.activations`` for last model layer
192
200
  (e.g. ``sigmoid``, ``softmax``, ``linear``).
193
- weights: optional, path to model weights.
201
+ weights: optional, path to model weights to be loaded.
202
+ weights_notop: optional, path to model weights without top (without segmentation
203
+ head) to be loaded.
194
204
  encoder_weights: one of ``None`` (random initialization), ``imagenet``
195
205
  (pre-training on ImageNet).
196
206
  encoder_freeze: if ``True`` set all layers of encoder (backbone model) as
@@ -245,6 +255,7 @@ def Unet(
245
255
  activation=activation,
246
256
  n_upsample_blocks=len(decoder_filters),
247
257
  use_batchnorm=decoder_use_batchnorm,
258
+ weights_notop=weights_notop,
248
259
  )
249
260
 
250
261
  # lock encoder weights for fine-tuning
@@ -1,8 +1,40 @@
1
1
  """Utility functions for segmentation models"""
2
2
 
3
+ from pathlib import Path
4
+
3
5
  from keras import models
4
6
 
5
7
 
8
+ def save_model_weights_notop(
9
+ model: models.Model, decoder: str, path: str | Path, overwrite: bool = True
10
+ ):
11
+ """Save model weights without top (without segmentation head).
12
+
13
+ The weights saved like this can be used to preload a segmentation model for
14
+ fine-tuning by passing the path to these weights to ``weights_notop`` argument of
15
+ the model constructor, e.g. ``Unet(weights_notop="path/to/weights.h5")``.
16
+
17
+ Args:
18
+ model (``keras.models.Model``): instance of keras model
19
+ decoder: type of the decoder part of the model. Should be one of ``fpn``,
20
+ ``linknet``, ``unet``, ``pspnet``.
21
+ path (str | Path): path to save model weights
22
+ overwrite (bool): whether to overwrite existing file at ``path``.
23
+ Defaults to ``True``.
24
+
25
+ """
26
+ decoder_top_layers = {"fpn": 2, "linknet": 2, "unet": 2, "pspnet": 3}
27
+ if decoder not in decoder_top_layers:
28
+ raise ValueError(
29
+ f"Decoder should be one of {decoder_top_layers.keys()}, got {decoder}"
30
+ )
31
+
32
+ # Add 1 to the slice to actually remove the number of top layers specified
33
+ nb_top_layers = decoder_top_layers[decoder] + 1
34
+ model_notop = models.Model(model.input, model.layers[-nb_top_layers].output)
35
+ model_notop.save_weights(str(path), overwrite=overwrite)
36
+
37
+
6
38
  def set_trainable(model, recompile=True, **kwargs): # noqa: ARG001
7
39
  """Set all layers of model trainable and recompile it
8
40
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: segmodels_keras
3
- Version: 0.1.0.dev4
3
+ Version: 0.1.0.dev5
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
@@ -106,5 +106,45 @@ def test_activity_reg(case):
106
106
  _test_regularizer(model, reg_model, x, y)
107
107
 
108
108
 
109
+ @pytest.mark.parametrize("decoder", ["fpn", "linknet", "unet", "pspnet"])
110
+ def test_save_model_weights_notop(tmp_path, decoder):
111
+ backbone_name = "mobilenetv2"
112
+ if decoder == "pspnet":
113
+ model = sm.PSPNet(backbone_name, encoder_weights=None)
114
+ elif decoder == "fpn":
115
+ model = sm.FPN(backbone_name, encoder_weights=None)
116
+ elif decoder == "linknet":
117
+ model = sm.Linknet(backbone_name, encoder_weights=None)
118
+ elif decoder == "unet":
119
+ model = sm.Unet(backbone_name, encoder_weights=None)
120
+ else:
121
+ raise ValueError(f"Incorrect decoder {decoder}")
122
+
123
+ # Test saving the model weights without the top layers
124
+ path = tmp_path / f"{decoder}.weights.h5"
125
+ sm.utils.save_model_weights_notop(model, decoder=decoder, path=path)
126
+
127
+ # Test loading the model weights without the top layers again
128
+ assert path.exists()
129
+ if decoder == "pspnet":
130
+ model = sm.PSPNet(backbone_name, weights_notop=path)
131
+ elif decoder == "fpn":
132
+ model = sm.FPN(backbone_name, weights_notop=path)
133
+ elif decoder == "linknet":
134
+ model = sm.Linknet(backbone_name, weights_notop=path)
135
+ elif decoder == "unet":
136
+ model = sm.Unet(backbone_name, weights_notop=path)
137
+ else:
138
+ raise ValueError(f"Incorrect decoder {decoder}")
139
+
140
+
141
+ def test_save_model_weights_notop_invalid_decoder():
142
+ model = keras.models.Model()
143
+ with pytest.raises(ValueError, match="Decoder should be one of"):
144
+ sm.utils.save_model_weights_notop(
145
+ model, decoder="invalid_decoder", path="path/to/weights.h5"
146
+ )
147
+
148
+
109
149
  if __name__ == "__main__":
110
150
  pytest.main([__file__])