Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modification of the definition of networks #18

Open
wants to merge 63 commits into
base: doc_refactor
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
28ad980
Update installation packages and installation instructions
SeguinBe Oct 6, 2018
957cd58
Revamp of the network description and architecture in a more flexible…
SeguinBe Oct 12, 2018
ff1edd8
Removing useless files
SeguinBe Oct 12, 2018
a9e0ed7
dh_segment_train as a script
SeguinBe Oct 12, 2018
e0d6c5d
Correcting the deletion of the main script, oops...
SeguinBe Oct 12, 2018
cb1d8fc
Nicer labels for the progress bars
SeguinBe Oct 12, 2018
da1258a
Nicer handling of number of threads
SeguinBe Oct 12, 2018
4de57fe
Removing code which has been made useless
SeguinBe Oct 12, 2018
7e5ccb4
mainly docstring formatting
solivr Oct 22, 2018
ce214c2
changed :param: by :ivar:
solivr Oct 22, 2018
62ec71d
Updating batchnorm training
SeguinBe Oct 26, 2018
cace550
Added MobileNetV2
SeguinBe Oct 26, 2018
ea11126
Documentation of exported model
SeguinBe Oct 29, 2018
82a5f22
Fixed refactoring
Oct 30, 2018
91540f2
Merge pull request #19 from sriak/master
solivr Oct 30, 2018
9889c7d
updated demo
solivr Nov 1, 2018
4e00913
pip install
solivr Nov 2, 2018
4f177b1
typo in attribute
solivr Nov 14, 2018
932fa3c
corrected non exported segment_ids field
solivr Nov 14, 2018
c5a1965
sorting of TextLines in a TextRegion
solivr Nov 15, 2018
346e2fb
force type to be int (for JSON export compatibility)
solivr Nov 20, 2018
7c25b56
specific to int32 and int64 type
solivr Nov 20, 2018
3eefba8
input csv file
solivr Dec 4, 2018
455a8e9
via annotation processing
e-maud Dec 11, 2018
811af9c
via annotation processing - typo
e-maud Dec 11, 2018
48efe87
type correction
solivr Dec 11, 2018
f736aaa
added doc
solivr Dec 12, 2018
7f65ad4
updated doc
solivr Jan 17, 2019
4509bc5
updated installation doc
solivr Jan 18, 2019
e61079f
packages versions
solivr Jan 18, 2019
db46c35
detected contour should have at least 3 points
solivr Jan 21, 2019
7c53e27
LatestExporter if no eval data is provided
solivr Jan 24, 2019
e07f996
update
solivr Dec 14, 2018
b090906
contour option in mask creation
solivr Jan 24, 2019
ba92f50
export regions coordinates to VIA compatible format
solivr Jan 30, 2019
fbb9350
doc and typos
solivr Feb 5, 2019
600acaa
simlified via.py and updated doc
solivr Feb 11, 2019
665af99
doc formatting
solivr Feb 11, 2019
84ec4dd
parse attributes of TextRegion and TextLines 'custom' and 'type'
solivr Dec 4, 2018
77bb4f3
remove git repo dependency
solivr Feb 11, 2019
532131a
merging
solivr Feb 11, 2019
909e8b1
corrected wrong argument names
solivr Feb 13, 2019
6717332
wrong variable name
solivr Feb 13, 2019
704087a
via example and doc formatting
solivr Feb 12, 2019
04ce8b6
Correcting typo masks creation script
alix-tz Feb 20, 2019
2264cf1
Merge pull request #26 from alix-tz/patch-1
solivr Feb 21, 2019
1262b59
Fixing instruction
alix-tz Feb 26, 2019
12d2759
Merge pull request #27 from alix-tz/patch-2
solivr Feb 28, 2019
6fdfcbd
do not export attribute 'type' if it's empty
solivr Mar 7, 2019
8fbd882
array to list of Point method
solivr Feb 25, 2019
2af56f2
update parsing + get list of tags from xml
solivr Mar 12, 2019
7100855
merge from master
SeguinBe Mar 22, 2019
8deae44
miou metric
solivr Mar 8, 2019
540eb36
to_json method for Page class
solivr Apr 4, 2019
605a930
updated via helpers
solivr Apr 9, 2019
6456a69
update packages version
solivr Apr 9, 2019
a072442
update to opencv 4.0
solivr Apr 9, 2019
fbad361
changelog
solivr Apr 9, 2019
9de5ca7
fix tensorflow-gpu version
solivr Apr 10, 2019
875c547
fixes #37
solivr May 15, 2019
7f2a348
merge
SeguinBe May 22, 2019
de461a7
working version corrected
SeguinBe May 22, 2019
1b36fca
formatting
solivr Jul 26, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions dh_segment/network/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
_MODEL = [
'inference_vgg16',
'inference_resnet_v1_50',
'inference_u_net',
'vgg_16_fn',
'resnet_v1_50_fn'
'Encoder',
'Decoder',
'SimpleDecoder',
]

__all__ = _MODEL
_PRETRAINED = [
'ResnetV1_50',
'VGG16'
]
__all__ = _MODEL + _PRETRAINED

from .model import *
from .pretrained_models import *
20 changes: 15 additions & 5 deletions dh_segment/network/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ def __call__(self, images: tf.Tensor) -> List[tf.Tensor]:
"""

:param images: [NxHxWx3] float32 [0..255] input images
:return: a list of the feature maps in decreasing spatial resolution (first element is most likely the input
:return: a list of the feature maps in decreasing spatial resolution (first element is most likely the input \
image itself, then the output of the first pooling op, etc...)
"""
pass

def pretrained_information(self) -> Tuple[Optional[str], Union[None, List, Dict]]:
"""

:return: The filename of the pretrained checkpoint and the corresponding variables (List of Dict mapping)
:return: The filename of the pretrained checkpoint and the corresponding variables (List of Dict mapping) \
or `None` if no-pretraining is done
"""
return None, None
Expand All @@ -32,14 +32,23 @@ class Decoder(ABC):
def __call__(self, feature_maps: List[tf.Tensor], num_classes: int) -> tf.Tensor:
"""

:param feature_maps: list of feature maps, in decreasing spatial resolution, first one being at the original resolution
:param feature_maps: list of feature maps, in decreasing spatial resolution, first one being at the original \
resolution
:return: [N,H,W,num_classes] float32 tensor of logit scores
"""
pass


class SimpleDecoder(Decoder):
def __init__(self, upsampling_dims: List[int], max_depth: int = None, train_batchnorm=False, weight_decay=0.):
"""

:param upsampling_dims:
:param max_depth:
:param weight_decay:
:param self.batch_norm_fn:
"""
def __init__(self, upsampling_dims: List[int], max_depth: int = None, train_batchnorm: bool=False,
weight_decay: float=0.):
self.upsampling_dims = upsampling_dims
self.max_depth = max_depth
self.weight_decay = weight_decay
Expand Down Expand Up @@ -105,6 +114,7 @@ def __call__(self, feature_maps: List[tf.Tensor], num_classes: int):
def _get_image_shape_tensor(tensor: tf.Tensor) -> Union[Tuple[int, int], tf.Tensor]:
"""
Get the image shape of the tensor

:param tensor: Input image tensor [N,H,W,...]
:return: a (int, int) tuple if shape is defined, otherwise the corresponding tf.Tensor value
"""
Expand All @@ -116,7 +126,7 @@ def _get_image_shape_tensor(tensor: tf.Tensor) -> Union[Tuple[int, int], tf.Tens
return target_shape


def _upsample_concat(pooled_layer: tf.Tensor, previous_layer: tf.Tensor, scope_name='UpsampleConcat'):
def _upsample_concat(pooled_layer: tf.Tensor, previous_layer: tf.Tensor, scope_name: str='UpsampleConcat'):
"""

:param pooled_layer: [N,H,W,C] coarse layer
Expand Down
65 changes: 40 additions & 25 deletions dh_segment/network/pretrained_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@ def mean_substraction(input_tensor, means=_VGG_MEANS):


class ResnetV1_50(Encoder):
def __init__(self, train_batchnorm=False, blocks=4, weight_decay=0.0001,
renorm=True, corrected_version=False):
"""ResNet-50 implementation

:param train_batchnorm: Option to use batch norm
:param blocks: number of blocks (resnet blocks)
:param weight_decay: value of weight decay
:param batch_renorm: Option to use batch renorm
:param corrected_version: option to use the original resnet implementation (True) but less efficient than
`slim`'s implementation
:param pretrained_file: path to the file (.ckpt) containing the pretrained weights
"""
def __init__(self, train_batchnorm: bool=False, blocks: int=4, weight_decay: float=0.0001,
batch_renorm: bool=True, corrected_version: bool=False):
self.train_batchnorm = train_batchnorm
self.blocks = blocks
self.weight_decay = weight_decay
self.renorm = renorm
self.batch_renorm = batch_renorm
self.corrected_version = corrected_version
self.pretrained_file = os.path.join(get_data_folder(), 'resnet_v1_50.ckpt')
if not os.path.exists(self.pretrained_file):
Expand All @@ -43,23 +53,21 @@ def __call__(self, images: tf.Tensor):
outputs = []

with slim.arg_scope(nets.resnet_v1.resnet_arg_scope(weight_decay=self.weight_decay, batch_norm_decay=0.999)), \
slim.arg_scope([layers.batch_norm], renorm_decay=0.95, renorm=self.renorm):
slim.arg_scope([layers.batch_norm], renorm_decay=0.95, renorm=self.batch_renorm):
mean_substracted_tensor = mean_substraction(images)
assert 0 < self.blocks <= 4

if self.corrected_version:
def corrected_resnet_v1_block(scope, base_depth, num_units, stride):
"""Helper function for creating a resnet_v1 bottleneck block.

Args:
scope: The scope of the block.
base_depth: The depth of the bottleneck layer for each unit.
num_units: The number of units in the block.
stride: The stride of the block, implemented as a stride in the last unit.
All other units have stride=1.

Returns:
A resnet_v1 bottleneck block.
def corrected_resnet_v1_block(scope: str, base_depth: int, num_units: int, stride: int) -> tf.Tensor:
"""
Helper function for creating a resnet_v1 bottleneck block.

:param scope: The scope of the block.
:param base_depth: The depth of the bottleneck layer for each unit.
:param num_units: The number of units in the block.
:param stride: The stride of the block, implemented as a stride in the last unit.
All other units have stride=1.
:return: A resnet_v1 bottleneck block.
"""
return nets.resnet_utils.Block(scope, nets.resnet_v1.bottleneck, [{
'depth': base_depth * 4,
Expand Down Expand Up @@ -119,7 +127,13 @@ def corrected_resnet_v1_block(scope, base_depth, num_units, stride):


class VGG16(Encoder):
def __init__(self, blocks=5, weight_decay=0.0005):
"""VGG-16 implementation

:param blocks: number of blocks (vgg blocks)
:param weight_decay: weight decay value
:param pretrained_file: path to the file (.ckpt) containing the pretrained weights
"""
def __init__(self, blocks: int=5, weight_decay: float=0.0005):
self.blocks = blocks
self.weight_decay = weight_decay
self.pretrained_file = os.path.join(get_data_folder(), 'vgg_16.ckpt')
Expand All @@ -140,36 +154,37 @@ def pretrained_information(self):
and 'renorm' not in v.name]

def __call__(self, images: tf.Tensor):
intermediate_levels = []
outputs = []

with slim.arg_scope(nets.vgg.vgg_arg_scope(weight_decay=self.weight_decay)):
with tf.variable_scope(None, 'vgg_16', [images]) as sc:
input_tensor = mean_substraction(images)
intermediate_levels.append(input_tensor)
outputs.append(input_tensor)
end_points_collection = sc.original_name_scope + '_end_points'
# Collect outputs for conv2d, fully_connected and max_pool2d.
with slim.arg_scope(
[layers.conv2d, layers.fully_connected, layers.max_pool2d],
outputs_collections=end_points_collection):
net = layers.repeat(
input_tensor, 2, layers.conv2d, 64, [3, 3], scope='conv1')
intermediate_levels.append(net)
outputs.append(net)
net = layers.max_pool2d(net, [2, 2], scope='pool1')
if self.blocks >= 2:
net = layers.repeat(net, 2, layers.conv2d, 128, [3, 3], scope='conv2')
intermediate_levels.append(net)
outputs.append(net)
net = layers.max_pool2d(net, [2, 2], scope='pool2')
if self.blocks >= 3:
net = layers.repeat(net, 3, layers.conv2d, 256, [3, 3], scope='conv3')
intermediate_levels.append(net)
outputs.append(net)
net = layers.max_pool2d(net, [2, 2], scope='pool3')
if self.blocks >= 4:
net = layers.repeat(net, 3, layers.conv2d, 512, [3, 3], scope='conv4')
intermediate_levels.append(net)
outputs.append(net)
net = layers.max_pool2d(net, [2, 2], scope='pool4')
if self.blocks >= 5:
net = layers.repeat(net, 3, layers.conv2d, 512, [3, 3], scope='conv5')
intermediate_levels.append(net)
outputs.append(net)
net = layers.max_pool2d(net, [2, 2], scope='pool5')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output of the last max_pool2d is not returned, shouldn't it be ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmhh... good catch. In a way, it depends what we want to do with the model definition, it can make sense to keep the feature maps with the best resolution but the lower receptive field. But I think if we want to be consistent with what we had, outputs should have the pooled versions, thoughts?


return intermediate_levels
# TODO : the output of the last max pool is not returned, shouldn't it be ?
return outputs
3 changes: 0 additions & 3 deletions dh_segment/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
_PARAMSCONFIG = [
'PredictionType',
'VGG16ModelParams',
'ResNetModelParams',
'UNetModelParams',
'ModelParams',
'TrainingParams'
]
Expand Down
7 changes: 7 additions & 0 deletions dh_segment/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def get_class_from_name(full_class_name: str) -> Any:
"""
Tries to load the class from its naming, will import the corresponding module.
Raises an Error if it does not work.

:param full_class_name: full name of the class, for instance `foo.bar.Baz`
:return: the loaded class
"""
Expand All @@ -67,6 +68,12 @@ def get_data_folder() -> str:


def download_file(url: str, output_file: str):
"""

:param url:
:param output_file:
:return:
"""
def progress_hook(t):
last_b = [0]

Expand Down
10 changes: 9 additions & 1 deletion dh_segment/utils/params_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class PredictionType:
MULTILABEL = 'MULTILABEL'

@classmethod
def parse(cls, prediction_type):
def parse(cls, prediction_type) -> 'PredictionType':
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this diff, but I wonder if we use Enum we don't get a parse method for free?

if prediction_type == 'CLASSIFICATION':
return PredictionType.CLASSIFICATION
elif prediction_type == 'REGRESSION':
Expand Down Expand Up @@ -43,6 +43,14 @@ def check_params(self):


class ModelParams(BaseParams):
"""

:param encoder_name:
:param encoder_params:
:param decoder_name:
:param decoder_params:
:param n_classes:
"""
def __init__(self, **kwargs):
self.encoder_name = kwargs.get('encoder_name', 'dh_segment.network.pretrained_models.ResnetV1_50') # type: str
self.encoder_params = kwargs.get('encoder_params', dict()) # type: dict
Expand Down