segmodels-keras 0.1.0.dev4__tar.gz → 0.1.0.dev6__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.dev6}/PKG-INFO +1 -1
  2. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/README.rst +4 -1
  3. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/_version.py +1 -1
  4. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/models/fpn.py +19 -1
  5. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/models/linknet.py +19 -1
  6. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/models/pspnet.py +19 -1
  7. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/models/unet.py +19 -1
  8. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/utils.py +32 -0
  9. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras.egg-info/PKG-INFO +1 -1
  10. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/tests/test_utils.py +40 -0
  11. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/LICENSE +0 -0
  12. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/MANIFEST.in +0 -0
  13. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/pyproject.toml +0 -0
  14. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/__init__.py +0 -0
  15. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/_compat.py +0 -0
  16. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/backbones/__init__.py +0 -0
  17. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/backbones/backbones_factory.py +0 -0
  18. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/backbones/inception_resnet_v2.py +0 -0
  19. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/backbones/inception_v3.py +0 -0
  20. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/base/__init__.py +0 -0
  21. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/base/functional.py +0 -0
  22. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/base/objects.py +0 -0
  23. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/losses.py +0 -0
  24. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/metrics.py +0 -0
  25. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/models/__init__.py +0 -0
  26. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/models/_common_blocks.py +0 -0
  27. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras/models/_utils.py +0 -0
  28. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras.egg-info/SOURCES.txt +0 -0
  29. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras.egg-info/dependency_links.txt +0 -0
  30. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras.egg-info/requires.txt +0 -0
  31. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/segmodels_keras.egg-info/top_level.txt +0 -0
  32. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/setup.cfg +0 -0
  33. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/setup.py +0 -0
  34. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/tests/test_backbones.py +0 -0
  35. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/tests/test_metrics.py +0 -0
  36. {segmodels_keras-0.1.0.dev4 → segmodels_keras-0.1.0.dev6}/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.dev6
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, "dev6")
2
2
  __version__ = ".".join(map(str, __versioninfo__))
@@ -107,6 +107,8 @@ def build_fpn(
107
107
  use_batchnorm=True,
108
108
  aggregation="sum",
109
109
  dropout=None,
110
+ weights_notop=None,
111
+ freeze_notop=False,
110
112
  ):
111
113
  input_ = backbone.input
112
114
  x = backbone.output
@@ -172,6 +174,14 @@ def build_fpn(
172
174
  size=(2, 2), interpolation="bilinear", name="final_upsampling"
173
175
  )(x)
174
176
 
177
+ # load weights without top if path provided
178
+ if weights_notop is not None:
179
+ model_notop = models.Model(input_, x)
180
+ model_notop.load_weights(weights_notop)
181
+ if freeze_notop:
182
+ freeze_model(model_notop)
183
+ x = model_notop.output
184
+
175
185
  # model head (define number of output classes)
176
186
  x = layers.Conv2D(
177
187
  filters=classes,
@@ -200,6 +210,8 @@ def FPN(
200
210
  classes=21,
201
211
  activation="softmax",
202
212
  weights=None,
213
+ weights_notop=None,
214
+ freeze_notop=False,
203
215
  encoder_weights="imagenet",
204
216
  encoder_freeze=False,
205
217
  encoder_features="default",
@@ -219,7 +231,11 @@ def FPN(
219
231
  your model be able to process images af any size, but ``H`` and ``W`` of
220
232
  input images should be divisible by factor ``32``.
221
233
  classes: a number of classes for output (output shape - ``(h, w, classes)``).
222
- weights: optional, path to model weights.
234
+ weights: optional, path to model weights to be loaded.
235
+ weights_notop: optional, path to model weights without top (without segmentation
236
+ head) to be loaded.
237
+ freeze_notop: if ``True``, set all layers of the model except the top as
238
+ non-trainable.
223
239
  activation: name of one of ``keras.activations`` for last model layer
224
240
  (e.g. ``sigmoid``, ``softmax``, ``linear``).
225
241
  encoder_weights: one of ``None`` (random initialization), ``imagenet``
@@ -265,6 +281,8 @@ def FPN(
265
281
  activation=activation,
266
282
  classes=classes,
267
283
  aggregation=pyramid_aggregation,
284
+ weights_notop=weights_notop,
285
+ freeze_notop=freeze_notop,
268
286
  )
269
287
 
270
288
  # lock encoder weights for fine-tuning
@@ -141,6 +141,8 @@ def build_linknet(
141
141
  classes=1,
142
142
  activation="sigmoid",
143
143
  use_batchnorm=True,
144
+ weights_notop=None,
145
+ freeze_notop=False,
144
146
  ):
145
147
  input_ = backbone.input
146
148
  x = backbone.output
@@ -169,6 +171,14 @@ def build_linknet(
169
171
  x, skip
170
172
  )
171
173
 
174
+ # load weights without top if path provided
175
+ if weights_notop is not None:
176
+ model_notop = models.Model(input_, x)
177
+ model_notop.load_weights(weights_notop)
178
+ if freeze_notop:
179
+ freeze_model(model_notop)
180
+ x = model_notop.output
181
+
172
182
  # model head (define number of output classes)
173
183
  x = layers.Conv2D(
174
184
  filters=classes,
@@ -196,6 +206,8 @@ def Linknet(
196
206
  classes=1,
197
207
  activation="sigmoid",
198
208
  weights=None,
209
+ weights_notop=None,
210
+ freeze_notop=False,
199
211
  encoder_weights="imagenet",
200
212
  encoder_freeze=False,
201
213
  encoder_features="default",
@@ -219,7 +231,11 @@ def Linknet(
219
231
  classes: a number of classes for output (output shape - ``(h, w, classes)``).
220
232
  activation: name of one of ``keras.activations`` for last model layer
221
233
  (e.g. ``sigmoid``, ``softmax``, ``linear``).
222
- weights: optional, path to model weights.
234
+ weights: optional, path to model weights to be loaded.
235
+ weights_notop: optional, path to model weights without top (without segmentation
236
+ head) to be loaded.
237
+ freeze_notop: if ``True``, set all layers of the model except the top as
238
+ non-trainable.
223
239
  encoder_weights: one of ``None`` (random initialization), ``imagenet``
224
240
  (pre-training on ImageNet).
225
241
  encoder_freeze: if ``True`` set all layers of encoder (backbone model) as
@@ -274,6 +290,8 @@ def Linknet(
274
290
  activation=activation,
275
291
  n_upsample_blocks=len(decoder_filters),
276
292
  use_batchnorm=decoder_use_batchnorm,
293
+ weights_notop=weights_notop,
294
+ freeze_notop=freeze_notop,
277
295
  )
278
296
 
279
297
  # lock encoder weights for fine-tuning
@@ -124,6 +124,8 @@ def build_psp(
124
124
  classes=21,
125
125
  activation="softmax",
126
126
  dropout=None,
127
+ weights_notop=None,
128
+ freeze_notop=False,
127
129
  ):
128
130
  input_ = backbone.input
129
131
  x = (
@@ -147,6 +149,14 @@ def build_psp(
147
149
  if dropout is not None:
148
150
  x = layers.SpatialDropout2D(dropout, name="spatial_dropout")(x)
149
151
 
152
+ # load weights without top if path provided
153
+ if weights_notop is not None:
154
+ model_notop = models.Model(input_, x)
155
+ model_notop.load_weights(weights_notop)
156
+ if freeze_notop:
157
+ freeze_model(model_notop)
158
+ x = model_notop.output
159
+
150
160
  # model head
151
161
  x = layers.Conv2D(
152
162
  filters=classes,
@@ -177,6 +187,8 @@ def PSPNet(
177
187
  classes=21,
178
188
  activation="softmax",
179
189
  weights=None,
190
+ weights_notop=None,
191
+ freeze_notop=False,
180
192
  encoder_weights="imagenet",
181
193
  encoder_freeze=False,
182
194
  downsample_factor=8,
@@ -197,7 +209,11 @@ def PSPNet(
197
209
  classes: a number of classes for output (output shape - ``(h, w, classes)``).
198
210
  activation: name of one of ``keras.activations`` for last model layer
199
211
  (e.g. ``sigmoid``, ``softmax``, ``linear``).
200
- weights: optional, path to model weights.
212
+ weights: optional, path to model weights to be loaded.
213
+ weights_notop: optional, path to model weights without top (without segmentation
214
+ head) to be loaded.
215
+ freeze_notop: if ``True``, set all layers of the model except the top as
216
+ non-trainable.
201
217
  encoder_weights: one of ``None`` (random initialization), ``imagenet``
202
218
  (pre-training on ImageNet).
203
219
  encoder_freeze: if ``True`` set all layers of encoder (backbone model) as
@@ -250,6 +266,8 @@ def PSPNet(
250
266
  classes=classes,
251
267
  activation=activation,
252
268
  dropout=psp_dropout,
269
+ weights_notop=weights_notop,
270
+ freeze_notop=freeze_notop,
253
271
  )
254
272
 
255
273
  # lock encoder weights for fine-tuning
@@ -113,6 +113,8 @@ def build_unet(
113
113
  classes=1,
114
114
  activation="sigmoid",
115
115
  use_batchnorm=True,
116
+ weights_notop=None,
117
+ freeze_notop=False,
116
118
  ):
117
119
  input_ = backbone.input
118
120
  x = backbone.output
@@ -141,6 +143,14 @@ def build_unet(
141
143
  x, skip
142
144
  )
143
145
 
146
+ # load weights without top if path provided
147
+ if weights_notop is not None:
148
+ model_notop = models.Model(input_, x)
149
+ model_notop.load_weights(weights_notop)
150
+ if freeze_notop:
151
+ freeze_model(model_notop)
152
+ x = model_notop.output
153
+
144
154
  # model head (define number of output classes)
145
155
  x = layers.Conv2D(
146
156
  filters=classes,
@@ -169,6 +179,8 @@ def Unet(
169
179
  classes=1,
170
180
  activation="sigmoid",
171
181
  weights=None,
182
+ weights_notop=None,
183
+ freeze_notop=False,
172
184
  encoder_weights="imagenet",
173
185
  encoder_freeze=False,
174
186
  encoder_features="default",
@@ -190,7 +202,11 @@ def Unet(
190
202
  classes: a number of classes for output (output shape - ``(h, w, classes)``).
191
203
  activation: name of one of ``keras.activations`` for last model layer
192
204
  (e.g. ``sigmoid``, ``softmax``, ``linear``).
193
- weights: optional, path to model weights.
205
+ weights: optional, path to model weights to be loaded.
206
+ weights_notop: optional, path to model weights without top (without segmentation
207
+ head) to be loaded.
208
+ freeze_notop: if ``True``, set all layers of the model except the top as
209
+ non-trainable.
194
210
  encoder_weights: one of ``None`` (random initialization), ``imagenet``
195
211
  (pre-training on ImageNet).
196
212
  encoder_freeze: if ``True`` set all layers of encoder (backbone model) as
@@ -245,6 +261,8 @@ def Unet(
245
261
  activation=activation,
246
262
  n_upsample_blocks=len(decoder_filters),
247
263
  use_batchnorm=decoder_use_batchnorm,
264
+ weights_notop=weights_notop,
265
+ freeze_notop=freeze_notop,
248
266
  )
249
267
 
250
268
  # 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.dev6
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, freeze_notop=True)
131
+ elif decoder == "fpn":
132
+ model = sm.FPN(backbone_name, weights_notop=path, freeze_notop=True)
133
+ elif decoder == "linknet":
134
+ model = sm.Linknet(backbone_name, weights_notop=path, freeze_notop=True)
135
+ elif decoder == "unet":
136
+ model = sm.Unet(backbone_name, weights_notop=path, freeze_notop=True)
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__])