diff --git a/.pfnci/config.pbtxt b/.pfnci/config.pbtxt new file mode 100644 index 0000000000..0afe603701 --- /dev/null +++ b/.pfnci/config.pbtxt @@ -0,0 +1,167 @@ +configs { + key: "chainercv.py2.stable" + value { + requirement { + cpu: 32 + memory: 64 + } + time_limit: { + seconds: 900 + } + command: "sh .pfnci/tests.sh" + environment_variables { + key: "PYTHON" + value: "2" + } + environment_variables { + key: "CHAINER" + value: "5.2.0" + } + } +} + +configs { + key: "chainercv.py3.stable" + value { + requirement { + cpu: 32 + memory: 64 + } + time_limit: { + seconds: 900 + } + command: "sh .pfnci/tests.sh" + environment_variables { + key: "PYTHON" + value: "3" + } + environment_variables { + key: "CHAINER" + value: "5.2.0" + } + } +} + +configs { + key: "chainercv.py2.latest" + value { + requirement { + cpu: 32 + memory: 64 + } + time_limit: { + seconds: 900 + } + command: "sh .pfnci/tests.sh" + environment_variables { + key: "PYTHON" + value: "2" + } + environment_variables { + key: "CHAINER" + value: "6.0.0b2" + } + } +} + +configs { + key: "chainercv.py3.latest" + value { + requirement { + cpu: 32 + memory: 64 + } + time_limit: { + seconds: 900 + } + command: "sh .pfnci/tests.sh" + environment_variables { + key: "PYTHON" + value: "3" + } + environment_variables { + key: "CHAINER" + value: "6.0.0b2" + } + } +} + +configs { + key: "chainercv.py2.stable.gpu" + value { + requirement { + cpu: 4 + memory: 16 + gpu: 1 + } + command: "sh .pfnci/tests_gpu.sh" + environment_variables { + key: "PYTHON" + value: "2" + } + environment_variables { + key: "CHAINER" + value: "5.2.0" + } + } +} + +configs { + key: "chainercv.py3.stable.gpu" + value { + requirement { + cpu: 4 + memory: 16 + gpu: 1 + } + command: "sh .pfnci/tests_gpu.sh" + environment_variables { + key: "PYTHON" + value: "3" + } + environment_variables { + key: "CHAINER" + value: "5.2.0" + } + } +} + +configs { + key: "chainercv.py2.latest.gpu" + value { + requirement { + cpu: 4 + memory: 16 + gpu: 1 + } + command: "sh .pfnci/tests_gpu.sh" + environment_variables { + key: "PYTHON" + value: "2" + } + environment_variables { + key: "CHAINER" + value: "6.0.0b2" + } + } +} + +configs { + key: "chainercv.py3.latest.gpu" + value { + requirement { + cpu: 4 + memory: 16 + gpu: 1 + } + command: "sh .pfnci/tests_gpu.sh" + environment_variables { + key: "PYTHON" + value: "3" + } + environment_variables { + key: "CHAINER" + value: "6.0.0b2" + } + } +} diff --git a/.pfnci/tests.sh b/.pfnci/tests.sh new file mode 100644 index 0000000000..e53ee883e2 --- /dev/null +++ b/.pfnci/tests.sh @@ -0,0 +1,24 @@ +#! /usr/bin/env sh +set -eux + +TEMP=$(mktemp -d) +mount -t tmpfs tmpfs ${TEMP}/ -o size=100% +apt-get install -y --no-install-recommends unzip +gsutil -q cp gs://chainercv-pfn-public-ci/datasets-tiny.zip ${TEMP}/ +unzip -q ${TEMP}/datasets-tiny.zip -d ${TEMP}/ +rm ${TEMP}/datasets-tiny.zip + +docker run --interactive --rm \ + --volume $(pwd):/chainercv/ --workdir /chainercv/ \ + --volume ${TEMP}/.chainer/:/root/.chainer/ \ + --env MPLBACKEND=agg \ + hakuyume/chainercv:chainer${CHAINER}-devel \ + sh -ex << EOD +pip${PYTHON} install --user pytest-xdist +pip${PYTHON} install --user -e . +python${PYTHON} -m pytest --color=no -n $(nproc) \ + -m 'not pfnci_skip and not gpu and not mpi' tests/ +mpiexec -n 2 --allow-run-as-root \ + python${PYTHON} -m pytest --color=no \ + -m 'not pfnci_skip and not gpu and mpi' tests/ +EOD diff --git a/.pfnci/tests_gpu.sh b/.pfnci/tests_gpu.sh new file mode 100644 index 0000000000..ae3657b8d9 --- /dev/null +++ b/.pfnci/tests_gpu.sh @@ -0,0 +1,20 @@ +#! /usr/bin/env sh +set -eux + +TEMP=$(mktemp -d) +mount -t tmpfs tmpfs ${TEMP}/ -o size=100% +mkdir -p ${TEMP}/.chainer + +docker run --runtime=nvidia --interactive --rm \ + --volume $(pwd):/chainercv/ --workdir /chainercv/ \ + --volume ${TEMP}/.chainer/:/root/.chainer/ \ + --env MPLBACKEND=agg \ + hakuyume/chainercv:chainer${CHAINER}-devel \ + sh -ex << EOD +pip${PYTHON} install --user -e . +python${PYTHON} -m pytest --color=no \ + -m 'not pfnci_skip and gpu and not mpi' tests/ +mpiexec -n 2 --allow-run-as-root \ + python${PYTHON} -m pytest --color=no \ + -m 'not pfnci_skip and gpu and mpi' tests/ +EOD diff --git a/chainercv/evaluations/eval_detection_voc.py b/chainercv/evaluations/eval_detection_voc.py index 80363351dc..2dc7d6eef6 100644 --- a/chainercv/evaluations/eval_detection_voc.py +++ b/chainercv/evaluations/eval_detection_voc.py @@ -19,34 +19,12 @@ def eval_detection_voc( The code is based on the evaluation code used in PASCAL VOC Challenge. Args: - pred_bboxes (iterable of numpy.ndarray): An iterable of :math:`N` - sets of bounding boxes. - Its index corresponds to an index for the base dataset. - Each element of :obj:`pred_bboxes` is a set of coordinates - of bounding boxes. This is an array whose shape is :math:`(R, 4)`, - where :math:`R` corresponds - to the number of bounding boxes, which may vary among boxes. - The second axis corresponds to - :math:`y_{min}, x_{min}, y_{max}, x_{max}` of a bounding box. - pred_labels (iterable of numpy.ndarray): An iterable of labels. - Similar to :obj:`pred_bboxes`, its index corresponds to an - index for the base dataset. Its length is :math:`N`. - pred_scores (iterable of numpy.ndarray): An iterable of confidence - scores for predicted bounding boxes. Similar to :obj:`pred_bboxes`, - its index corresponds to an index for the base dataset. - Its length is :math:`N`. - gt_bboxes (iterable of numpy.ndarray): An iterable of ground truth - bounding boxes - whose length is :math:`N`. An element of :obj:`gt_bboxes` is a - bounding box whose shape is :math:`(R, 4)`. Note that the number of - bounding boxes in each image does not need to be same as the number - of corresponding predicted boxes. - gt_labels (iterable of numpy.ndarray): An iterable of ground truth - labels which are organized similarly to :obj:`gt_bboxes`. - gt_difficults (iterable of numpy.ndarray): An iterable of boolean - arrays which is organized similarly to :obj:`gt_bboxes`. - This tells whether the - corresponding ground truth bounding box is difficult or not. + pred_bboxes (iterable of numpy.ndarray): See the table below. + pred_labels (iterable of numpy.ndarray): See the table below. + pred_scores (iterable of numpy.ndarray): See the table below. + gt_bboxes (iterable of numpy.ndarray): See the table below. + gt_labels (iterable of numpy.ndarray): See the table below. + gt_difficults (iterable of numpy.ndarray): See the table below. By default, this is :obj:`None`. In that case, this function considers all bounding boxes to be not difficult. iou_thresh (float): A prediction is correct if its Intersection over @@ -55,6 +33,21 @@ def eval_detection_voc( for calculating average precision. The default value is :obj:`False`. + .. csv-table:: + :header: name, shape, dtype, format + + :obj:`pred_bboxes`, ":math:`[(R, 4)]`", :obj:`float32`, \ + ":math:`(y_{min}, x_{min}, y_{max}, x_{max})`" + :obj:`pred_labels`, ":math:`[(R,)]`", :obj:`int32`, \ + ":math:`[0, \#fg\_class - 1]`" + :obj:`pred_scores`, ":math:`[(R,)]`", :obj:`float32`, \ + -- + :obj:`gt_bboxes`, ":math:`[(R, 4)]`", :obj:`float32`, \ + ":math:`(y_{min}, x_{min}, y_{max}, x_{max})`" + :obj:`gt_labels`, ":math:`[(R,)]`", :obj:`int32`, \ + ":math:`[0, \#fg\_class - 1]`" + :obj:`gt_difficults`, ":math:`[(R,)]`", :obj:`bool`, -- + Returns: dict: @@ -92,34 +85,18 @@ def calc_detection_voc_prec_rec( The code is based on the evaluation code used in PASCAL VOC Challenge. Args: - pred_bboxes (iterable of numpy.ndarray): An iterable of :math:`N` - sets of bounding boxes. - Its index corresponds to an index for the base dataset. - Each element of :obj:`pred_bboxes` is a set of coordinates - of bounding boxes. This is an array whose shape is :math:`(R, 4)`, - where :math:`R` corresponds - to the number of bounding boxes, which may vary among boxes. - The second axis corresponds to - :math:`y_{min}, x_{min}, y_{max}, x_{max}` of a bounding box. - pred_labels (iterable of numpy.ndarray): An iterable of labels. - Similar to :obj:`pred_bboxes`, its index corresponds to an - index for the base dataset. Its length is :math:`N`. - pred_scores (iterable of numpy.ndarray): An iterable of confidence - scores for predicted bounding boxes. Similar to :obj:`pred_bboxes`, - its index corresponds to an index for the base dataset. - Its length is :math:`N`. - gt_bboxes (iterable of numpy.ndarray): An iterable of ground truth - bounding boxes - whose length is :math:`N`. An element of :obj:`gt_bboxes` is a - bounding box whose shape is :math:`(R, 4)`. Note that the number of - bounding boxes in each image does not need to be same as the number - of corresponding predicted boxes. - gt_labels (iterable of numpy.ndarray): An iterable of ground truth - labels which are organized similarly to :obj:`gt_bboxes`. - gt_difficults (iterable of numpy.ndarray): An iterable of boolean - arrays which is organized similarly to :obj:`gt_bboxes`. - This tells whether the - corresponding ground truth bounding box is difficult or not. + pred_bboxes (iterable of numpy.ndarray): See the table in + :func:`chainercv.evaluations.eval_detection_voc`. + pred_labels (iterable of numpy.ndarray): See the table in + :func:`chainercv.evaluations.eval_detection_voc`. + pred_scores (iterable of numpy.ndarray): See the table in + :func:`chainercv.evaluations.eval_detection_voc`. + gt_bboxes (iterable of numpy.ndarray): See the table in + :func:`chainercv.evaluations.eval_detection_voc`. + gt_labels (iterable of numpy.ndarray): See the table in + :func:`chainercv.evaluations.eval_detection_voc`. + gt_difficults (iterable of numpy.ndarray): See the table in + :func:`chainercv.evaluations.eval_detection_voc`. By default, this is :obj:`None`. In that case, this function considers all bounding boxes to be not difficult. iou_thresh (float): A prediction is correct if its Intersection over diff --git a/chainercv/evaluations/eval_instance_segmentation_voc.py b/chainercv/evaluations/eval_instance_segmentation_voc.py index 823808c60d..9cde351c43 100644 --- a/chainercv/evaluations/eval_instance_segmentation_voc.py +++ b/chainercv/evaluations/eval_instance_segmentation_voc.py @@ -21,33 +21,29 @@ def eval_instance_segmentation_voc( .. _`FCIS`: https://arxiv.org/abs/1611.07709 Args: - pred_masks (iterable of numpy.ndarray): An iterable of :math:`N` - sets of masks. Its index corresponds to an index for the base - dataset. Each element of :obj:`pred_masks` is an object mask - and is an array whose shape is :math:`(R, H, W)`, - where :math:`R` corresponds - to the number of masks, which may vary among images. - pred_labels (iterable of numpy.ndarray): An iterable of labels. - Similar to :obj:`pred_masks`, its index corresponds to an - index for the base dataset. Its length is :math:`N`. - pred_scores (iterable of numpy.ndarray): An iterable of confidence - scores for predicted masks. Similar to :obj:`pred_masks`, - its index corresponds to an index for the base dataset. - Its length is :math:`N`. - gt_masks (iterable of numpy.ndarray): An iterable of ground truth - masks whose length is :math:`N`. An element of :obj:`gt_masks` is - an object mask whose shape is :math:`(R, H, W)`. Note that the - number of masks :math:`R` in each image does not need to be - same as the number of corresponding predicted masks. - gt_labels (iterable of numpy.ndarray): An iterable of ground truth - labels which are organized similarly to :obj:`gt_masks`. Its - length is :math:`N`. + pred_masks (iterable of numpy.ndarray): See the table below. + pred_labels (iterable of numpy.ndarray): See the table below. + pred_scores (iterable of numpy.ndarray): See the table below. + gt_masks (iterable of numpy.ndarray): See the table below. + gt_labels (iterable of numpy.ndarray): See the table below. iou_thresh (float): A prediction is correct if its Intersection over Union with the ground truth is above this value. use_07_metric (bool): Whether to use PASCAL VOC 2007 evaluation metric for calculating average precision. The default value is :obj:`False`. + .. csv-table:: + :header: name, shape, dtype, format + + :obj:`pred_masks`, ":math:`[(R, H, W)]`", :obj:`bool`, -- + :obj:`pred_labels`, ":math:`[(R,)]`", :obj:`int32`, \ + ":math:`[0, \#fg\_class - 1]`" + :obj:`pred_scores`, ":math:`[(R,)]`", :obj:`float32`, \ + -- + :obj:`gt_masks`, ":math:`[(R, H, W)]`", :obj:`bool`, -- + :obj:`gt_labels`, ":math:`[(R,)]`", :obj:`int32`, \ + ":math:`[0, \#fg\_class - 1]`" + Returns: dict: diff --git a/chainercv/evaluations/eval_semantic_segmentation.py b/chainercv/evaluations/eval_semantic_segmentation.py index ac72521247..296e2ff1f0 100644 --- a/chainercv/evaluations/eval_semantic_segmentation.py +++ b/chainercv/evaluations/eval_semantic_segmentation.py @@ -12,15 +12,10 @@ def calc_semantic_segmentation_confusion(pred_labels, gt_labels): the maximum class id of the inputs added by one. Args: - pred_labels (iterable of numpy.ndarray): A collection of predicted - labels. The shape of a label array - is :math:`(H, W)`. :math:`H` and :math:`W` - are height and width of the label. - gt_labels (iterable of numpy.ndarray): A collection of ground - truth labels. The shape of a ground truth label array is - :math:`(H, W)`, and its corresponding prediction label should - have the same shape. - A pixel with value :obj:`-1` will be ignored during evaluation. + pred_labels (iterable of numpy.ndarray): See the table in + :func:`chainercv.evaluations.eval_semantic_segmentation`. + gt_labels (iterable of numpy.ndarray): See the table in + :func:`chainercv.evaluations.eval_semantic_segmentation`. Returns: numpy.ndarray: @@ -136,18 +131,16 @@ class :math:`j` by the prediction. `_. arXiv 2017. Args: - pred_labels (iterable of numpy.ndarray): A collection of predicted - labels. The shape of a label array - is :math:`(H, W)`. :math:`H` and :math:`W` - are height and width of the label. - For example, this is a list of labels - :obj:`[label_0, label_1, ...]`, where - :obj:`label_i.shape = (H_i, W_i)`. - gt_labels (iterable of numpy.ndarray): A collection of ground - truth labels. The shape of a ground truth label array is - :math:`(H, W)`, and its corresponding prediction label should - have the same shape. - A pixel with value :obj:`-1` will be ignored during evaluation. + pred_labels (iterable of numpy.ndarray): See the table below. + gt_labels (iterable of numpy.ndarray): See the table below. + + .. csv-table:: + :header: name, shape, dtype, format + + :obj:`pred_labels`, ":math:`[(H, W)]`", :obj:`int32`, \ + ":math:`[0, \#class - 1]`" + :obj:`gt_labels`, ":math:`[(H, W)]`", :obj:`int32`, \ + ":math:`[-1, \#class - 1]`" Returns: dict: diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index d65e14375f..72b4d32106 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -11,8 +11,8 @@ from chainercv.links.model.faster_rcnn.faster_rcnn_vgg import FasterRCNNVGG16 # NOQA from chainercv.links.model.fpn.faster_rcnn_fpn_resnet import FasterRCNNFPNResNet101 # NOQA from chainercv.links.model.fpn.faster_rcnn_fpn_resnet import FasterRCNNFPNResNet50 # NOQA -from chainercv.links.model.mask_rcnn.mask_rcnn_fpn_resnet import MaskRCNNFPNResNet101 # NOQA -from chainercv.links.model.mask_rcnn.mask_rcnn_fpn_resnet import MaskRCNNFPNResNet50 # NOQA +from chainercv.links.model.fpn.faster_rcnn_fpn_resnet import MaskRCNNFPNResNet101 # NOQA +from chainercv.links.model.fpn.faster_rcnn_fpn_resnet import MaskRCNNFPNResNet50 # NOQA from chainercv.links.model.resnet import ResNet101 # NOQA from chainercv.links.model.resnet import ResNet152 # NOQA from chainercv.links.model.resnet import ResNet50 # NOQA diff --git a/chainercv/links/model/deeplab/deeplab_v3_plus.py b/chainercv/links/model/deeplab/deeplab_v3_plus.py index 8f530c1a04..1778d6ae8f 100644 --- a/chainercv/links/model/deeplab/deeplab_v3_plus.py +++ b/chainercv/links/model/deeplab/deeplab_v3_plus.py @@ -183,10 +183,10 @@ def predict(self, imgs): with chainer.using_config('train', False), \ chainer.function.no_backprop_mode(): labels = [] - score = 0 n_aug = len(self.scales) if self.flip else len(self.scales) * 2 for img in imgs: + score = 0 for scale in self.scales: score += self._get_proba(img, scale, False) / n_aug if self.flip: @@ -270,7 +270,11 @@ def __init__(self, n_class=None, pretrained_model=None, 'extractor_kwargs': extractor_kwargs, 'aspp_kwargs': aspp_kwargs, 'decoder_kwargs': decoder_kwargs}, pretrained_model, self._models, - default={'min_input_size': (513, 513)}) + default={ + 'min_input_size': (513, 513), + 'scales': (1.0,), 'flip': False, + 'extractor_kwargs': {}, + 'aspp_kwargs': {}, 'decoder_kwargs': {}}) super(DeepLabV3plusXception65, self).__init__( Xception65(**param['extractor_kwargs']), diff --git a/chainercv/links/model/fpn/__init__.py b/chainercv/links/model/fpn/__init__.py index 0ceacd4fe5..7f2f16d62e 100644 --- a/chainercv/links/model/fpn/__init__.py +++ b/chainercv/links/model/fpn/__init__.py @@ -1,9 +1,14 @@ from chainercv.links.model.fpn.faster_rcnn import FasterRCNN # NOQA from chainercv.links.model.fpn.faster_rcnn_fpn_resnet import FasterRCNNFPNResNet101 # NOQA from chainercv.links.model.fpn.faster_rcnn_fpn_resnet import FasterRCNNFPNResNet50 # NOQA +from chainercv.links.model.fpn.faster_rcnn_fpn_resnet import MaskRCNNFPNResNet101 # NOQA +from chainercv.links.model.fpn.faster_rcnn_fpn_resnet import MaskRCNNFPNResNet50 # NOQA from chainercv.links.model.fpn.fpn import FPN # NOQA -from chainercv.links.model.fpn.head import Head # NOQA -from chainercv.links.model.fpn.head import head_loss_post # NOQA -from chainercv.links.model.fpn.head import head_loss_pre # NOQA +from chainercv.links.model.fpn.bbox_head import BboxHead # NOQA +from chainercv.links.model.fpn.bbox_head import bbox_loss_post # NOQA +from chainercv.links.model.fpn.bbox_head import bbox_loss_pre # NOQA +from chainercv.links.model.fpn.mask_head import MaskHead # NOQA +from chainercv.links.model.fpn.mask_head import mask_loss_post # NOQA +from chainercv.links.model.fpn.mask_head import mask_loss_pre # NOQA from chainercv.links.model.fpn.rpn import RPN # NOQA from chainercv.links.model.fpn.rpn import rpn_loss # NOQA diff --git a/chainercv/links/model/fpn/head.py b/chainercv/links/model/fpn/bbox_head.py similarity index 96% rename from chainercv/links/model/fpn/head.py rename to chainercv/links/model/fpn/bbox_head.py index 12c85b7b31..1daa8897b0 100644 --- a/chainercv/links/model/fpn/head.py +++ b/chainercv/links/model/fpn/bbox_head.py @@ -13,8 +13,8 @@ from chainercv import utils -class Head(chainer.Chain): - """Head network of Feature Pyramid Networks. +class BboxHead(chainer.Chain): + """Bounding box head network of Feature Pyramid Networks. Args: n_class (int): The number of classes including background. @@ -28,7 +28,7 @@ class Head(chainer.Chain): std = (0.1, 0.2) def __init__(self, n_class, scales): - super(Head, self).__init__() + super(BboxHead, self).__init__() fc_init = { 'initialW': Caffe2FCUniform(), @@ -210,10 +210,10 @@ def decode(self, rois, roi_indices, locs, confs, return bboxes, labels, scores -def head_loss_pre(rois, roi_indices, std, bboxes, labels): +def bbox_loss_pre(rois, roi_indices, std, bboxes, labels): """Loss function for Head (pre). - This function processes RoIs for :func:`head_loss_post`. + This function processes RoIs for :func:`bbox_head_loss_post`. Args: rois (iterable of arrays): An iterable of arrays of @@ -305,7 +305,7 @@ def head_loss_pre(rois, roi_indices, std, bboxes, labels): return rois, roi_indices, gt_locs, gt_labels -def head_loss_post(locs, confs, roi_indices, gt_locs, gt_labels, batchsize): +def bbox_loss_post(locs, confs, roi_indices, gt_locs, gt_labels, batchsize): """Loss function for Head (post). Args: @@ -314,11 +314,11 @@ def head_loss_post(locs, confs, roi_indices, gt_locs, gt_labels, batchsize): confs (array): An iterable of arrays whose shape is :math:`(R, n\_class)`. roi_indices (list of arrays): A list of arrays returned by - :func:`head_locs_pre`. + :func:`bbox_head_locs_pre`. gt_locs (list of arrays): A list of arrays returned by - :func:`head_locs_pre`. + :func:`bbox_head_locs_pre`. gt_labels (list of arrays): A list of arrays returned by - :func:`head_locs_pre`. + :func:`bbox_head_locs_pre`. batchsize (int): The size of batch. Returns: diff --git a/chainercv/links/model/fpn/faster_rcnn.py b/chainercv/links/model/fpn/faster_rcnn.py index 23611b3313..68b4506233 100644 --- a/chainercv/links/model/fpn/faster_rcnn.py +++ b/chainercv/links/model/fpn/faster_rcnn.py @@ -3,9 +3,10 @@ import numpy as np import chainer +import chainer.functions as F from chainer.backends import cuda -from chainercv import transforms +from chainercv.links.model.fpn.misc import scale_img class FasterRCNN(chainer.Chain): @@ -23,9 +24,17 @@ class FasterRCNN(chainer.Chain): rpn (Link): A link that has the same interface as :class:`~chainercv.links.model.fpn.RPN`. Please refer to the documentation found there. - head (Link): A link that has the same interface as - :class:`~chainercv.links.model.fpn.Head`. + bbox_head (Link): A link that has the same interface as + :class:`~chainercv.links.model.fpn.BboxHead`. Please refer to the documentation found there. + mask_head (Link): A link that has the same interface as + :class:`~chainercv.links.model.mask_rcnn.MaskRCNN`. + Please refer to the documentation found there. + min_size (int): A preprocessing paramter for :meth:`prepare`. Please + refer to a docstring found for :meth:`prepare`. + max_size (int): A preprocessing paramter for :meth:`prepare`. Note + that the result of :meth:`prepare` can exceed this size due to + alignment with stride. Parameters: nms_thresh (float): The threshold value @@ -40,16 +49,35 @@ class FasterRCNN(chainer.Chain): """ - _min_size = 800 - _max_size = 1333 - _stride = 32 - - def __init__(self, extractor, rpn, head): + stride = 32 + _accepted_return_values = ('rois', 'bboxes', 'labels', 'scores', 'masks') + + def __init__(self, extractor, rpn, bbox_head, + mask_head, return_values, + min_size=800, max_size=1333): + for value_name in return_values: + if value_name not in self._accepted_return_values: + raise ValueError( + '{} is not included in accepted value names {}'.format( + value_name, self._accepted_return_values)) + self._return_values = return_values + + self._store_rpn_outputs = 'rois' in self._return_values + self._run_bbox = any([key in self._return_values + for key in ['bboxes', 'labels', 'scores', 'masks']]) + self._run_mask = 'masks' in self._return_values super(FasterRCNN, self).__init__() + with self.init_scope(): self.extractor = extractor self.rpn = rpn - self.head = head + if self._run_bbox: + self.bbox_head = bbox_head + if self._run_mask: + self.mask_head = mask_head + + self.min_size = min_size + self.max_size = max_size self.use_preset('visualize') @@ -87,52 +115,92 @@ def __call__(self, x): anchors = self.rpn.anchors(h.shape[2:] for h in hs) rois, roi_indices = self.rpn.decode( rpn_locs, rpn_confs, anchors, x.shape) - rois, roi_indices = self.head.distribute(rois, roi_indices) - head_locs, head_confs = self.head(hs, rois, roi_indices) - return rois, roi_indices, head_locs, head_confs + return hs, rois, roi_indices def predict(self, imgs): - """Detect objects from images. + """Segment object instances from images. - This method predicts objects for each image. + This method predicts instance-aware object regions for each image. Args: - imgs (iterable of numpy.ndarray): Arrays holding images. - All images are in CHW and RGB format + imgs (iterable of numpy.ndarray): Arrays holding images of shape + :math:`(B, C, H, W)`. All images are in CHW and RGB format and the range of their value is :math:`[0, 255]`. Returns: tuple of lists: This method returns a tuple of three lists, - :obj:`(bboxes, labels, scores)`. + :obj:`(masks, labels, scores)`. - * **bboxes**: A list of float arrays of shape :math:`(R, 4)`, \ - where :math:`R` is the number of bounding boxes in a image. \ - Each bounding box is organized by \ - :math:`(y_{min}, x_{min}, y_{max}, x_{max})` \ - in the second axis. + * **masks**: A list of boolean arrays of shape :math:`(R, H, W)`, \ + where :math:`R` is the number of masks in a image. \ + Each pixel holds value if it is inside the object inside or not. * **labels** : A list of integer arrays of shape :math:`(R,)`. \ - Each value indicates the class of the bounding box. \ + Each value indicates the class of the masks. \ Values are in range :math:`[0, L - 1]`, where :math:`L` is the \ number of the foreground classes. * **scores** : A list of float arrays of shape :math:`(R,)`. \ Each value indicates how confident the prediction is. """ + output = {} sizes = [img.shape[1:] for img in imgs] x, scales = self.prepare(imgs) with chainer.using_config('train', False), chainer.no_backprop_mode(): - rois, roi_indices, head_locs, head_confs = self(x) - bboxes, labels, scores = self.head.decode( - rois, roi_indices, head_locs, head_confs, - scales, sizes, self.nms_thresh, self.score_thresh) - - bboxes = [cuda.to_cpu(bbox) for bbox in bboxes] - labels = [cuda.to_cpu(label) for label in labels] - scores = [cuda.to_cpu(score) for score in scores] - return bboxes, labels, scores + hs, rpn_rois, rpn_roi_indices = self(x) + if self._store_rpn_outputs: + rpn_rois_cpu = [ + chainer.backends.cuda.to_cpu(rpn_roi) / scale + for rpn_roi, scale in + zip(_flat_to_list(rpn_rois, rpn_roi_indices, len(imgs)), + scales)] + output.update({'rois': rpn_rois_cpu}) + + if self._run_bbox: + bbox_rois, bbox_roi_indices = self.bbox_head.distribute( + rpn_rois, rpn_roi_indices) + with chainer.using_config( + 'train', False), chainer.no_backprop_mode(): + head_locs, head_confs = self.bbox_head( + hs, bbox_rois, bbox_roi_indices) + bboxes, labels, scores = self.bbox_head.decode( + bbox_rois, bbox_roi_indices, head_locs, head_confs, + scales, sizes, self.nms_thresh, self.score_thresh) + bboxes_cpu = [chainer.backends.cuda.to_cpu(bbox) + for bbox in bboxes] + labels_cpu = [chainer.backends.cuda.to_cpu(label) for label in labels] + scores_cpu = [cuda.to_cpu(score) for score in scores] + output.update({'bboxes': bboxes_cpu, 'labels': labels_cpu, + 'scores': scores_cpu}) + + if self._run_mask: + rescaled_bboxes = [bbox * scale + for scale, bbox in zip(scales, bboxes)] + # Change bboxes to RoI and RoI indices format + mask_rois_before_reordering, mask_roi_indices_before_reordering =\ + _list_to_flat(rescaled_bboxes) + mask_rois, mask_roi_indices, order = self.mask_head.distribute( + mask_rois_before_reordering, mask_roi_indices_before_reordering) + with chainer.using_config( + 'train', False), chainer.no_backprop_mode(): + segms = F.sigmoid( + self.mask_head(hs, mask_rois, mask_roi_indices)).data + # Put the order of proposals back to the one used by bbox head. + segms = segms[order] + segms = _flat_to_list( + segms, mask_roi_indices_before_reordering, len(imgs)) + segms = [segm if segm is not None else + self.xp.zeros( + (0, self.mask_head.segm_size, self.mask_head.segm_size), + dtype=np.float32) + for segm in segms] + segms = [chainer.backends.cuda.to_cpu(segm) for segm in segms] + # Currently MaskHead only supports numpy inputs + masks_cpu = self.mask_head.decode(segms, bboxes_cpu, labels_cpu, sizes) + output.update({'masks': masks_cpu}) + return tuple([output[key] for key in self._return_values]) def prepare(self, imgs): """Preprocess images. @@ -147,26 +215,44 @@ def prepare(self, imgs): scales that were caluclated in prepocessing. """ - scales = [] resized_imgs = [] for img in imgs: - _, H, W = img.shape - scale = self._min_size / min(H, W) - if scale * max(H, W) > self._max_size: - scale = self._max_size / max(H, W) - scales.append(scale) - H, W = int(H * scale), int(W * scale) - img = transforms.resize(img, (H, W)) + img, scale = scale_img( + img, self.min_size, self.max_size) img -= self.extractor.mean + scales.append(scale) resized_imgs.append(img) - - size = np.array([im.shape[1:] for im in resized_imgs]).max(axis=0) - size = (np.ceil(size / self._stride) * self._stride).astype(int) - x = np.zeros((len(imgs), 3, size[0], size[1]), dtype=np.float32) - for i, img in enumerate(resized_imgs): - _, H, W = img.shape - x[i, :, :H, :W] = img - + pad_size = np.array( + [im.shape[1:] for im in resized_imgs]).max(axis=0) + pad_size = ( + np.ceil(pad_size / self.stride) * self.stride).astype(int) + x = np.zeros( + (len(imgs), 3, pad_size[0], pad_size[1]), dtype=np.float32) + for i, im in enumerate(resized_imgs): + _, H, W = im.shape + x[i, :, :H, :W] = im x = self.xp.array(x) + return x, scales + + +def _list_to_flat(array_list): + xp = chainer.backends.cuda.get_array_module(array_list[0]) + + indices = xp.concatenate( + [i * xp.ones((len(array),), dtype=np.int32) for + i, array in enumerate(array_list)], axis=0) + flat = xp.concatenate(array_list, axis=0) + return flat, indices + + +def _flat_to_list(flat, indices, B): + array_list = [] + for i in range(B): + array = flat[indices == i] + if len(array) > 0: + array_list.append(array) + else: + array_list.append(None) + return array_list diff --git a/chainercv/links/model/fpn/faster_rcnn_fpn_resnet.py b/chainercv/links/model/fpn/faster_rcnn_fpn_resnet.py index 9757694d92..debadb10ea 100644 --- a/chainercv/links/model/fpn/faster_rcnn_fpn_resnet.py +++ b/chainercv/links/model/fpn/faster_rcnn_fpn_resnet.py @@ -6,7 +6,8 @@ from chainercv.links.model.fpn.faster_rcnn import FasterRCNN from chainercv.links.model.fpn.fpn import FPN -from chainercv.links.model.fpn.head import Head +from chainercv.links.model.fpn.bbox_head import BboxHead +from chainercv.links.model.fpn.mask_head import MaskHead from chainercv.links.model.fpn.rpn import RPN from chainercv.links.model.resnet import ResNet101 from chainercv.links.model.resnet import ResNet50 @@ -17,9 +18,35 @@ class FasterRCNNFPNResNet(FasterRCNN): """Base class for FasterRCNNFPNResNet50 and FasterRCNNFPNResNet101. A subclass of this class should have :obj:`_base` and :obj:`_models`. + + Args: + n_fg_class (int): The number of classes excluding the background. + pretrained_model (string): The weight file to be loaded. + This can take :obj:`'coco'`, `filepath` or :obj:`None`. + The default value is :obj:`None`. + + * :obj:`'coco'`: Load weights trained on train split of \ + MS COCO 2017. \ + The weight file is downloaded and cached automatically. \ + :obj:`n_fg_class` must be :obj:`80` or :obj:`None`. + * :obj:`'imagenet'`: Load weights of ResNet-50 trained on \ + ImageNet. \ + The weight file is downloaded and cached automatically. \ + This option initializes weights partially and the rests are \ + initialized randomly. In this case, :obj:`n_fg_class` \ + can be set to any number. + * `filepath`: A path of npz file. In this case, :obj:`n_fg_class` \ + must be specified properly. + * :obj:`None`: Do not load weights. + min_size (int): A preprocessing paramter for :meth:`prepare`. Please \ + refer to :meth:`prepare`. + max_size (int): A preprocessing paramter for :meth:`prepare`. + """ - def __init__(self, n_fg_class=None, pretrained_model=None): + def __init__(self, n_fg_class=None, pretrained_model=None, + return_values=['bboxes', 'labels', 'scores'], + min_size=800, max_size=1333): param, path = utils.prepare_pretrained_model( {'n_fg_class': n_fg_class}, pretrained_model, self._models) @@ -34,7 +61,10 @@ def __init__(self, n_fg_class=None, pretrained_model=None): super(FasterRCNNFPNResNet, self).__init__( extractor=extractor, rpn=RPN(extractor.scales), - head=Head(param['n_fg_class'] + 1, extractor.scales), + bbox_head=BboxHead(param['n_fg_class'] + 1, extractor.scales), + mask_head=MaskHead(param['n_fg_class'] + 1, extractor.scales), + return_values=return_values, + min_size=min_size, max_size=max_size ) if path == 'imagenet': @@ -45,7 +75,7 @@ def __init__(self, n_fg_class=None, pretrained_model=None): chainer.serializers.load_npz(path, self) -class FasterRCNNFPNResNet50(FasterRCNNFPNResNet): +class MaskRCNNFPNResNet(FasterRCNNFPNResNet): """Feature Pyramid Networks with ResNet-50. This is a model of Feature Pyramid Networks [#]_. @@ -55,25 +85,26 @@ class FasterRCNNFPNResNet50(FasterRCNNFPNResNet): .. [#] Tsung-Yi Lin et al. Feature Pyramid Networks for Object Detection. CVPR 2017 - Args: - n_fg_class (int): The number of classes excluding the background. - pretrained_model (string): The weight file to be loaded. - This can take :obj:`'coco'`, `filepath` or :obj:`None`. - The default value is :obj:`None`. - * :obj:`'coco'`: Load weights trained on train split of \ - MS COCO 2017. \ - The weight file is downloaded and cached automatically. \ - :obj:`n_fg_class` must be :obj:`80` or :obj:`None`. - * :obj:`'imagenet'`: Load weights of ResNet-50 trained on \ - ImageNet. \ - The weight file is downloaded and cached automatically. \ - This option initializes weights partially and the rests are \ - initialized randomly. In this case, :obj:`n_fg_class` \ - can be set to any number. - * `filepath`: A path of npz file. In this case, :obj:`n_fg_class` \ - must be specified properly. - * :obj:`None`: Do not load weights. + """ + + def __init__(self, n_fg_class=None, pretrained_model=None, + min_size=800, max_size=1333): + super(MaskRCNNFPNResNet, self).__init__( + n_fg_class, pretrained_model, ['masks', 'labels', 'scores'], + min_size, max_size) + + +class FasterRCNNFPNResNet50(FasterRCNNFPNResNet): + """Feature Pyramid Networks with ResNet-50. + + This is a model of Feature Pyramid Networks [#]_. + This model uses :class:`~chainercv.links.ResNet50` as + its base feature extractor. + + .. [#] Tsung-Yi Lin et al. + Feature Pyramid Networks for Object Detection. CVPR 2017 + """ @@ -82,7 +113,7 @@ class FasterRCNNFPNResNet50(FasterRCNNFPNResNet): 'coco': { 'param': {'n_fg_class': 80}, 'url': 'https://chainercv-models.preferred.jp/' - 'faster_rcnn_fpn_resnet50_coco_trained_2018_12_13.npz', + 'faster_rcnn_fpn_resnet50_coco_trained_2019_03_15.npz', 'cv2': True }, } @@ -98,39 +129,67 @@ class FasterRCNNFPNResNet101(FasterRCNNFPNResNet): .. [#] Tsung-Yi Lin et al. Feature Pyramid Networks for Object Detection. CVPR 2017 - Args: - n_fg_class (int): The number of classes excluding the background. - pretrained_model (string): The weight file to be loaded. - This can take :obj:`'coco'`, `filepath` or :obj:`None`. - The default value is :obj:`None`. + """ + + _base = ResNet101 + _models = { + 'coco': { + 'param': {'n_fg_class': 80}, + 'url': 'https://chainercv-models.preferred.jp/' + 'faster_rcnn_fpn_resnet101_coco_trained_2019_03_15.npz', + 'cv2': True + }, + } + + +class MaskRCNNFPNResNet50(MaskRCNNFPNResNet): + """Feature Pyramid Networks with ResNet-50. + + This is a model of Feature Pyramid Networks [#]_. + This model uses :class:`~chainercv.links.ResNet50` as + its base feature extractor. + + .. [#] Tsung-Yi Lin et al. + Feature Pyramid Networks for Object Detection. CVPR 2017 - * :obj:`'coco'`: Load weights trained on train split of \ - MS COCO 2017. \ - The weight file is downloaded and cached automatically. \ - :obj:`n_fg_class` must be :obj:`80` or :obj:`None`. - * :obj:`'imagenet'`: Load weights of ResNet-101 trained on \ - ImageNet. \ - The weight file is downloaded and cached automatically. \ - This option initializes weights partially and the rests are \ - initialized randomly. In this case, :obj:`n_fg_class` \ - can be set to any number. - * `filepath`: A path of npz file. In this case, :obj:`n_fg_class` \ - must be specified properly. - * :obj:`None`: Do not load weights. """ - _base = ResNet101 + _base = ResNet50 _models = { 'coco': { 'param': {'n_fg_class': 80}, 'url': 'https://chainercv-models.preferred.jp/' - 'faster_rcnn_fpn_resnet101_coco_trained_2018_12_13.npz', + 'faster_rcnn_fpn_resnet50_mask_coco_trained_2019_03_15.npz', 'cv2': True }, } +class MaskRCNNFPNResNet101(MaskRCNNFPNResNet): + """Feature Pyramid Networks with ResNet-50. + + This is a model of Feature Pyramid Networks [#]_. + This model uses :class:`~chainercv.links.ResNet50` as + its base feature extractor. + + .. [#] Tsung-Yi Lin et al. + Feature Pyramid Networks for Object Detection. CVPR 2017 + + + """ + + _base = ResNet101 + _models = { + 'coco': { + 'param': {'n_fg_class': 80}, + 'url': '', + 'cv2': True + }, + } + + + def _copyparams(dst, src): if isinstance(dst, chainer.Chain): for link in dst.children(): diff --git a/chainercv/links/model/mask_rcnn/mask_head.py b/chainercv/links/model/fpn/mask_head.py similarity index 98% rename from chainercv/links/model/mask_rcnn/mask_head.py rename to chainercv/links/model/fpn/mask_head.py index dc65fd6718..b89857fa5d 100644 --- a/chainercv/links/model/mask_rcnn/mask_head.py +++ b/chainercv/links/model/fpn/mask_head.py @@ -11,8 +11,8 @@ from chainercv.links import Conv2DActiv from chainercv.utils.bbox.bbox_iou import bbox_iou -from chainercv.links.model.mask_rcnn.misc import mask_to_segm -from chainercv.links.model.mask_rcnn.misc import segm_to_mask +from chainercv.links.model.fpn.mask_utils import mask_to_segm +from chainercv.links.model.fpn.mask_utils import segm_to_mask class MaskHead(chainer.Chain): diff --git a/chainercv/links/model/mask_rcnn/misc.py b/chainercv/links/model/fpn/mask_utils.py similarity index 90% rename from chainercv/links/model/mask_rcnn/misc.py rename to chainercv/links/model/fpn/mask_utils.py index 9e2ce8eade..c8cba87076 100644 --- a/chainercv/links/model/mask_rcnn/misc.py +++ b/chainercv/links/model/fpn/mask_utils.py @@ -1,6 +1,5 @@ from __future__ import division -import cv2 import numpy as np import chainer @@ -8,17 +7,6 @@ from chainercv import transforms -def scale_img(img, min_size, max_size): - """Process image.""" - _, H, W = img.shape - scale = min_size / min(H, W) - if scale * max(H, W) > max_size: - scale = max_size / max(H, W) - H, W = int(H * scale), int(W * scale) - img = transforms.resize(img, (H, W)) - return img, scale - - def mask_to_segm(mask, bbox, segm_size, index=None, pad=1): """Crop and resize mask. @@ -47,8 +35,8 @@ def mask_to_segm(mask, bbox, segm_size, index=None, pad=1): _, H, W = mask.shape bbox = chainer.backends.cuda.to_cpu(bbox) padded_segm_size = segm_size + pad * 2 - cv2_expand_scale = padded_segm_size / segm_size - bbox = _integerize_bbox(_expand_boxes(bbox, cv2_expand_scale)) + expand_scale = padded_segm_size / segm_size + bbox = _integerize_bbox(_expand_boxes(bbox, expand_scale)) segm = [] if index is None: @@ -115,11 +103,11 @@ def segm_to_mask(segm, bbox, size, pad=1): # pixel prior to resizing back to the original image resolution. # This prevents "top hat" artifacts. We therefore need to expand # the reference boxes by an appropriate factor. - cv2_expand_scale = (segm_size + pad * 2) / segm_size + expand_scale = (segm_size + pad * 2) / segm_size padded_mask = np.zeros( (segm_size + pad * 2, segm_size + pad * 2), dtype=np.float32) - bbox = _integerize_bbox(_expand_boxes(bbox, cv2_expand_scale)) + bbox = _integerize_bbox(_expand_boxes(bbox, expand_scale)) for i, (bb, sgm) in enumerate(zip(bbox, segm)): padded_mask[1:-1, 1:-1] = sgm @@ -128,7 +116,8 @@ def segm_to_mask(segm, bbox, size, pad=1): if bb_height == 0 or bb_width == 0: continue - crop_mask = transforms.resize(padded_mask[None], (bb_width, bb_height))[0] + crop_mask = transforms.resize( + padded_mask[None], (bb_height, bb_width))[0] crop_mask = crop_mask > 0.5 y_min = max(bb[0], 0) diff --git a/chainercv/links/model/fpn/misc.py b/chainercv/links/model/fpn/misc.py index 19204cf9c7..7863255ab9 100644 --- a/chainercv/links/model/fpn/misc.py +++ b/chainercv/links/model/fpn/misc.py @@ -5,6 +5,8 @@ from chainer.backends import cuda import chainer.functions as F +from chainercv import transforms + exp_clip = np.log(1000 / 16) @@ -48,3 +50,14 @@ def choice(x, size): return y else: return cuda.to_gpu(y) + + +def scale_img(img, min_size, max_size): + """Process image.""" + _, H, W = img.shape + scale = min_size / min(H, W) + if scale * max(H, W) > max_size: + scale = max_size / max(H, W) + H, W = int(H * scale), int(W * scale) + img = transforms.resize(img, (H, W)) + return img, scale diff --git a/chainercv/utils/image/read_image.py b/chainercv/utils/image/read_image.py index 6ea71b3216..2f2e448eec 100644 --- a/chainercv/utils/image/read_image.py +++ b/chainercv/utils/image/read_image.py @@ -44,11 +44,11 @@ def _read_image_cv2(file, dtype, color, alpha): else: color_option = cv2.IMREAD_GRAYSCALE - if isinstance(file, str): - img = cv2.imread(file, color_option) - else: + if hasattr(file, 'read'): b = np.array(bytearray(file.read())) img = cv2.imdecode(b, color_option) + else: + img = cv2.imread(file, color_option) if img.ndim == 2: # reshape (H, W) -> (1, H, W) diff --git a/chainercv/utils/testing/__init__.py b/chainercv/utils/testing/__init__.py index 648f6e1ba3..524cb71d61 100644 --- a/chainercv/utils/testing/__init__.py +++ b/chainercv/utils/testing/__init__.py @@ -1,3 +1,7 @@ +from chainer.testing import product # NOQA +from chainer.testing import product_dict # NOQA +from chainer.testing import run_module # NOQA + from chainercv.utils.testing.assertions import assert_is_bbox # NOQA from chainercv.utils.testing.assertions import assert_is_bbox_dataset # NOQA from chainercv.utils.testing.assertions import assert_is_detection_link # NOQA @@ -11,3 +15,4 @@ from chainercv.utils.testing.assertions import assert_is_semantic_segmentation_link # NOQA from chainercv.utils.testing.constant_stub_link import ConstantStubLink # NOQA from chainercv.utils.testing.generate_random_bbox import generate_random_bbox # NOQA +from chainercv.utils.testing.parameterized import parameterize # NOQA diff --git a/chainercv/utils/testing/assertions/assert_is_point_dataset.py b/chainercv/utils/testing/assertions/assert_is_point_dataset.py index 576f767e5f..c55f252b12 100644 --- a/chainercv/utils/testing/assertions/assert_is_point_dataset.py +++ b/chainercv/utils/testing/assertions/assert_is_point_dataset.py @@ -24,8 +24,8 @@ def assert_is_point_dataset(dataset, n_point=None, n_example=None, examples ramdomly and checks them. Otherwise, this function checks all examples. no_visible (bool): If :obj:`True`, we assume that - visibility mask is always not contained. - If :obj:`False`, point visible may or may not be contained. + :obj:`visible` is always not contained. + If :obj:`False`, :obj;`visible` may or may not be contained. """ diff --git a/chainercv/utils/testing/attr.py b/chainercv/utils/testing/attr.py new file mode 100644 index 0000000000..1caacac510 --- /dev/null +++ b/chainercv/utils/testing/attr.py @@ -0,0 +1,28 @@ +import unittest + +from chainer.testing.attr import check_available +from chainer.testing.attr import gpu # NOQA +from chainer.testing.attr import slow # NOQA + + +try: + import pytest + pfnci_skip = pytest.mark.pfnci_skip + +except ImportError: + from chainer.testing.attr import _dummy_callable + pfnci_skip = _dummy_callable + + +def mpi(f): + check_available() + import pytest + + try: + import mpi4py.MPI # NOQA + available = True + except ImportError: + available = False + + return unittest.skipUnless( + available, 'mpi4py is not installed')(pytest.mark.mpi(f)) diff --git a/chainercv/utils/testing/constant_stub_link.py b/chainercv/utils/testing/constant_stub_link.py index 05d5810623..07f3733e6a 100644 --- a/chainercv/utils/testing/constant_stub_link.py +++ b/chainercv/utils/testing/constant_stub_link.py @@ -58,6 +58,15 @@ def __call__(self, *_): tuple of :obj:`chainer.Variable`. """ + # TODO(Hakuyume): Remove this fix when 'to_device' APIs is refactored. + # Fix for Chainer 6.x. + # https://github.com/chainer/chainer/issues/6244 + for output in self._outputs: + if self.xp is np: + output.to_cpu() + else: + output.to_gpu() + if self._tuple: return self._outputs else: diff --git a/chainercv/utils/testing/parameterized.py b/chainercv/utils/testing/parameterized.py new file mode 100644 index 0000000000..82d06842c1 --- /dev/null +++ b/chainercv/utils/testing/parameterized.py @@ -0,0 +1,31 @@ +import six + +from chainer import testing + + +def parameterize(*params): + """:func:`chainer.testing.parameterize` for `pytest-xdist`. + + :func:`chainer.testing.parameterize` cannot work with `pytest-xdist` + when the params contain functions (lambdas), classes, and random values. + This wrapper replaces the params with their indices + and restore the original params in :meth:`setUp`. + """ + + def deco(cls): + setUp_orig = cls.setUp + + def setUp(self): + param = params[self._chainercv_parameterize_index] + print('params: {}'.format(param)) + for k, v in six.iteritems(param): + setattr(self, k, v) + setUp_orig(self) + + cls.setUp = setUp + + params_indices = [ + {'_chainercv_parameterize_index': i} for i in range(len(params))] + return testing.parameterize(*params_indices)(cls) + + return deco diff --git a/docs/source/reference/links/fpn.rst b/docs/source/reference/links/fpn.rst index d97aa3599f..5d267ff026 100644 --- a/docs/source/reference/links/fpn.rst +++ b/docs/source/reference/links/fpn.rst @@ -31,9 +31,9 @@ FPN .. autoclass:: FPN :members: -Head -~~~~ -.. autoclass:: Head +BboxHead +~~~~~~~~ +.. autoclass:: BboxHead :members: :special-members: __call__ @@ -46,13 +46,13 @@ RPN Train-only Utility ------------------ -head_loss_pre -~~~~~~~~~~~~~ -.. autofunction:: head_loss_pre +bbox_head_loss_pre +~~~~~~~~~~~~~~~~~~ +.. autofunction:: bbox_head_loss_pre -head_loss_post -~~~~~~~~~~~~~~ -.. autofunction:: head_loss_post +bbox_head_loss_post +~~~~~~~~~~~~~~~~~~~ +.. autofunction:: bbox_head_loss_post rpn_loss ~~~~~~~~ diff --git a/examples/classification/train_imagenet_multi.py b/examples/classification/train_imagenet_multi.py index 6de120aeda..607d364d77 100644 --- a/examples/classification/train_imagenet_multi.py +++ b/examples/classification/train_imagenet_multi.py @@ -4,7 +4,6 @@ import numpy as np import chainer -from chainer.datasets import TransformDataset from chainer import iterators from chainer.links import Classifier from chainer.optimizer import WeightDecay @@ -12,6 +11,7 @@ from chainer import training from chainer.training import extensions +from chainercv.chainer_experimental.datasets.sliceable import TransformDataset from chainercv.datasets import directory_parsing_label_names from chainercv.datasets import DirectoryParsingLabelDataset from chainercv.transforms import center_crop @@ -126,8 +126,9 @@ def main(): train_data = DirectoryParsingLabelDataset(args.train) val_data = DirectoryParsingLabelDataset(args.val) train_data = TransformDataset( - train_data, TrainTransform(extractor.mean)) - val_data = TransformDataset(val_data, ValTransform(extractor.mean)) + train_data, ('img', 'label'), TrainTransform(extractor.mean)) + val_data = TransformDataset( + val_data, ('img', 'label'), ValTransform(extractor.mean)) print('finished loading dataset') if comm.rank == 0: diff --git a/examples/fpn/demo.py b/examples/fpn/demo.py index 053d0351e2..0d615cacfb 100644 --- a/examples/fpn/demo.py +++ b/examples/fpn/demo.py @@ -4,17 +4,22 @@ import chainer from chainercv.datasets import coco_bbox_label_names +from chainercv.datasets import coco_instance_segmentation_label_names from chainercv.links import FasterRCNNFPNResNet101 from chainercv.links import FasterRCNNFPNResNet50 +from chainercv.links import MaskRCNNFPNResNet101 +from chainercv.links import MaskRCNNFPNResNet50 from chainercv import utils from chainercv.visualizations import vis_bbox +from chainercv.visualizations import vis_instance_segmentation def main(): parser = argparse.ArgumentParser() parser.add_argument( '--model', - choices=('faster_rcnn_fpn_resnet50', 'faster_rcnn_fpn_resnet101'), + choices=('faster_rcnn_fpn_resnet50', 'faster_rcnn_fpn_resnet101', + 'mask_rcnn_fpn_resnet50', 'mask_rcnn_fpn_resnet101'), default='faster_rcnn_fpn_resnet50') parser.add_argument('--gpu', type=int, default=-1) parser.add_argument('--pretrained-model', default='coco') @@ -22,26 +27,48 @@ def main(): args = parser.parse_args() if args.model == 'faster_rcnn_fpn_resnet50': + mode = 'bbox' model = FasterRCNNFPNResNet50( n_fg_class=len(coco_bbox_label_names), pretrained_model=args.pretrained_model) elif args.model == 'faster_rcnn_fpn_resnet101': + mode = 'bbox' model = FasterRCNNFPNResNet101( n_fg_class=len(coco_bbox_label_names), pretrained_model=args.pretrained_model) + elif args.model == 'mask_rcnn_fpn_resnet50': + mode = 'instance_segmentation' + model = MaskRCNNFPNResNet50( + n_fg_class=len(coco_instance_segmentation_label_names), + pretrained_model=args.pretrained_model) + elif args.model == 'mask_rcnn_fpn_resnet101': + mode = 'instance_segmentation' + model = MaskRCNNFPNResNet101( + n_fg_class=len(coco_instance_segmentation_label_names), + pretrained_model=args.pretrained_model) if args.gpu >= 0: chainer.cuda.get_device_from_id(args.gpu).use() model.to_gpu() img = utils.read_image(args.image) - bboxes, labels, scores = model.predict([img]) - bbox = bboxes[0] - label = labels[0] - score = scores[0] - vis_bbox( - img, bbox, label, score, label_names=coco_bbox_label_names) + if mode == 'bbox': + bboxes, labels, scores = model.predict([img]) + bbox = bboxes[0] + label = labels[0] + score = scores[0] + + vis_bbox( + img, bbox, label, score, label_names=coco_bbox_label_names) + elif mode == 'instance_segmentation': + masks, labels, scores = model.predict([img]) + mask = masks[0] + label = labels[0] + score = scores[0] + vis_instance_segmentation( + img, mask, label, score, + label_names=coco_instance_segmentation_label_names) plt.show() diff --git a/examples/fpn/train_multi.py b/examples/fpn/train_multi.py index 1adff045d8..9d3d08b633 100644 --- a/examples/fpn/train_multi.py +++ b/examples/fpn/train_multi.py @@ -1,10 +1,10 @@ -from __future__ import division - import argparse import multiprocessing import numpy as np +import PIL import chainer +import chainer.functions as F import chainer.links as L from chainer.optimizer_hooks import WeightDecay from chainer import serializers @@ -15,14 +15,23 @@ from chainercv.chainer_experimental.datasets.sliceable import TransformDataset from chainercv.chainer_experimental.training.extensions import make_shift +from chainercv.links.model.fpn.misc import scale_img +from chainercv import transforms + +from chainercv.datasets import coco_instance_segmentation_label_names +from chainercv.datasets import COCOInstanceSegmentationDataset +from chainercv.links import MaskRCNNFPNResNet101 +from chainercv.links import MaskRCNNFPNResNet50 + from chainercv.datasets import coco_bbox_label_names from chainercv.datasets import COCOBboxDataset from chainercv.links import FasterRCNNFPNResNet101 from chainercv.links import FasterRCNNFPNResNet50 -from chainercv import transforms -from chainercv.links.model.fpn import head_loss_post -from chainercv.links.model.fpn import head_loss_pre +from chainercv.links.model.fpn import bbox_loss_post +from chainercv.links.model.fpn import bbox_loss_pre +from chainercv.links.model.fpn import mask_loss_post +from chainercv.links.model.fpn import mask_loss_pre from chainercv.links.model.fpn import rpn_loss # https://docs.chainer.org/en/stable/tips.html#my-training-process-gets-stuck-when-using-multiprocessiterator @@ -40,11 +49,23 @@ def __init__(self, model): with self.init_scope(): self.model = model - def __call__(self, imgs, bboxes, labels): - x, scales = self.model.prepare(imgs) - bboxes = [self.xp.array(bbox) * scale - for bbox, scale in zip(bboxes, scales)] + def __call__(self, imgs, bboxes, labels, masks=None): + B = len(imgs) + pad_size = np.array( + [im.shape[1:] for im in imgs]).max(axis=0) + pad_size = ( + np.ceil( + pad_size / self.model.stride) * self.model.stride).astype(int) + x = np.zeros( + (len(imgs), 3, pad_size[0], pad_size[1]), dtype=np.float32) + for i, img in enumerate(imgs): + _, H, W = img.shape + x[i, :, :H, :W] = img + x = self.xp.array(x) + + bboxes = [self.xp.array(bbox) for bbox in bboxes] labels = [self.xp.array(label) for label in labels] + sizes = [img.shape[1:] for img in imgs] with chainer.using_config('train', False): hs = self.model.extractor(x) @@ -52,10 +73,7 @@ def __call__(self, imgs, bboxes, labels): rpn_locs, rpn_confs = self.model.rpn(hs) anchors = self.model.rpn.anchors(h.shape[2:] for h in hs) rpn_loc_loss, rpn_conf_loss = rpn_loss( - rpn_locs, rpn_confs, anchors, - [(int(img.shape[1] * scale), int(img.shape[2] * scale)) - for img, scale in zip(imgs, scales)], - bboxes) + rpn_locs, rpn_confs, anchors, sizes, bboxes) rois, roi_indices = self.model.rpn.decode( rpn_locs, rpn_confs, anchors, x.shape) @@ -64,33 +82,87 @@ def __call__(self, imgs, bboxes, labels): [roi_indices] + [self.xp.array((i,) * len(bbox)) for i, bbox in enumerate(bboxes)]) - rois, roi_indices = self.model.head.distribute(rois, roi_indices) - rois, roi_indices, head_gt_locs, head_gt_labels = head_loss_pre( - rois, roi_indices, self.model.head.std, bboxes, labels) - head_locs, head_confs = self.model.head(hs, rois, roi_indices) - head_loc_loss, head_conf_loss = head_loss_post( + rois, roi_indices = self.model.bbox_head.distribute(rois, roi_indices) + rois, roi_indices, head_gt_locs, head_gt_labels = bbox_loss_pre( + rois, roi_indices, self.model.bbox_head.std, bboxes, labels) + head_locs, head_confs = self.model.bbox_head(hs, rois, roi_indices) + head_loc_loss, head_conf_loss = bbox_loss_post( head_locs, head_confs, - roi_indices, head_gt_locs, head_gt_labels, len(x)) - - loss = rpn_loc_loss + rpn_conf_loss + head_loc_loss + head_conf_loss + roi_indices, head_gt_locs, head_gt_labels, B) + + mask_loss = 0 + if masks is not None: + # For reducing unnecessary CPU/GPU copy, `masks` is kept in CPU. + pad_masks = [ + np.zeros( + (mask.shape[0], pad_size[0], pad_size[1]), dtype=np.bool) + for mask in masks] + for i, mask in enumerate(masks): + _, H, W = mask.shape + pad_masks[i][:, :H, :W] = mask + masks = pad_masks + + mask_rois, mask_roi_indices, gt_segms, gt_mask_labels =\ + mask_loss_pre( + rois, roi_indices, masks, bboxes, + head_gt_labels, self.model.mask_head.segm_size) + n_roi = sum([len(roi) for roi in mask_rois]) + if n_roi > 0: + segms = self.model.mask_head(hs, mask_rois, mask_roi_indices) + mask_loss = mask_loss_post( + segms, mask_roi_indices, gt_segms, gt_mask_labels, B) + else: + # Compute dummy variables to complete the computational graph + mask_rois[0] = self.xp.array([[0, 0, 1, 1]], dtype=np.float32) + mask_roi_indices[0] = self.xp.array([0], dtype=np.int32) + segms = self.model.mask_head(hs, mask_rois, mask_roi_indices) + mask_loss = 0 * F.sum(segms) + loss = (rpn_loc_loss + rpn_conf_loss + + head_loc_loss + head_conf_loss + mask_loss) chainer.reporter.report({ 'loss': loss, 'loss/rpn/loc': rpn_loc_loss, 'loss/rpn/conf': rpn_conf_loss, - 'loss/head/loc': head_loc_loss, 'loss/head/conf': head_conf_loss}, + 'loss/bbox_head/loc': head_loc_loss, + 'loss/bbox_head/conf': head_conf_loss, + 'loss/mask_head': mask_loss}, self) - return loss -def transform(in_data): - img, bbox, label = in_data +class Transform(object): - img, params = transforms.random_flip( - img, x_random=True, return_param=True) - bbox = transforms.flip_bbox( - bbox, img.shape[1:], x_flip=params['x_flip']) + def __init__(self, min_size, max_size, mean): + self.min_size = min_size + self.max_size = max_size + self.mean = mean - return img, bbox, label + def __call__(self, in_data): + if len(in_data) == 4: + img, mask, label, bbox = in_data + else: + img, bbox, label = in_data + # Flipping + img, params = transforms.random_flip( + img, x_random=True, return_param=True) + x_flip = params['x_flip'] + bbox = transforms.flip_bbox( + bbox, img.shape[1:], x_flip=x_flip) + + # Scaling and mean subtraction + img, scale = scale_img( + img, self.min_size, self.max_size) + img -= self.mean + bbox = bbox * scale + + if len(in_data) == 4: + mask = transforms.flip(mask, x_flip=x_flip) + mask = transforms.resize( + mask.astype(np.float32), + img.shape[1:], + interpolation=PIL.Image.NEAREST).astype(np.bool) + return img, bbox, label, mask + else: + return img, bbox, label def converter(batch, device=None): @@ -100,15 +172,18 @@ def converter(batch, device=None): def main(): parser = argparse.ArgumentParser() + parser.add_argument('--data-dir', default='auto') parser.add_argument( '--model', - choices=('faster_rcnn_fpn_resnet50', 'faster_rcnn_fpn_resnet101'), - default='faster_rcnn_fpn_resnet50') + choices=('mask_rcnn_fpn_resnet50', 'mask_rcnn_fpn_resnet101', + 'faster_rcnn_fpn_resnet50', 'faster_rcnn_fpn_resnet101'), + default='faster__rcnn_fpn_resnet50') parser.add_argument('--batchsize', type=int, default=16) parser.add_argument('--iteration', type=int, default=90000) parser.add_argument('--step', type=int, nargs='*', default=[60000, 80000]) parser.add_argument('--out', default='result') parser.add_argument('--resume') + parser.add_argument('--communicator', default='hierarchical') args = parser.parse_args() # https://docs.chainer.org/en/stable/chainermn/tutorial/tips_faqs.html#using-multiprocessiterator @@ -118,24 +193,47 @@ def main(): p.start() p.join() - comm = chainermn.create_communicator() + comm = chainermn.create_communicator(args.communicator) device = comm.intra_rank if args.model == 'faster_rcnn_fpn_resnet50': + mode = 'bbox' model = FasterRCNNFPNResNet50( - n_fg_class=len(coco_bbox_label_names), pretrained_model='imagenet') + n_fg_class=len(coco_bbox_label_names), + pretrained_model='imagenet') elif args.model == 'faster_rcnn_fpn_resnet101': + mode = 'bbox' model = FasterRCNNFPNResNet101( - n_fg_class=len(coco_bbox_label_names), pretrained_model='imagenet') + n_fg_class=len(coco_bbox_label_names), + pretrained_model='imagenet') + elif args.model == 'mask_rcnn_fpn_resnet50': + mode = 'instance_segmentation' + model = MaskRCNNFPNResNet50( + n_fg_class=len(coco_instance_segmentation_label_names), + pretrained_model='imagenet') + elif args.model == 'mask_rcnn_fpn_resnet101': + mode = 'instance_segmentation' + model = MaskRCNNFPNResNet101( + n_fg_class=len(coco_instance_segmentation_label_names), + pretrained_model='imagenet') model.use_preset('evaluate') train_chain = TrainChain(model) chainer.cuda.get_device_from_id(device).use() train_chain.to_gpu() - train = TransformDataset( - COCOBboxDataset(year='2017', split='train'), - ('img', 'bbox', 'label'), transform) + if mode == 'bbox': + train = TransformDataset( + COCOBboxDataset( + data_dir=args.data_dir, year='2017', split='train'), + ('img', 'bbox', 'label'), + Transform(model.min_size, model.max_size, model.extractor.mean)) + elif mode == 'instance_segmentation': + train = TransformDataset( + COCOInstanceSegmentationDataset( + data_dir=args.data_dir, split='train', return_bbox=True), + ('img', 'bbox', 'label', 'mask'), + Transform(model.min_size, model.max_size, model.extractor.mean)) if comm.rank == 0: indices = np.arange(len(train)) @@ -144,8 +242,10 @@ def main(): indices = chainermn.scatter_dataset(indices, comm, shuffle=True) train = train.slice[indices] - train_iter = chainer.iterators.MultithreadIterator( - train, args.batchsize // comm.size) + train_iter = chainer.iterators.MultiprocessIterator( + train, args.batchsize // comm.size, + n_processes=args.batchsize // comm.size, + shared_mem=100 * 1000 * 1000 * 4) optimizer = chainermn.create_multi_node_optimizer( chainer.optimizers.MomentumSGD(), comm) @@ -158,10 +258,11 @@ def main(): if isinstance(link, L.BatchNormalization): link.disable_update() + n_iteration = args.iteration * 16 / args.batchsize updater = training.updaters.StandardUpdater( train_iter, optimizer, converter=converter, device=device) trainer = training.Trainer( - updater, (args.iteration * 16 / args.batchsize, 'iteration'), args.out) + updater, (n_iteration, 'iteration'), args.out) @make_shift('lr') def lr_schedule(trainer): @@ -190,7 +291,9 @@ def lr_schedule(trainer): trainer.extend(extensions.PrintReport( ['epoch', 'iteration', 'lr', 'main/loss', 'main/loss/rpn/loc', 'main/loss/rpn/conf', - 'main/loss/head/loc', 'main/loss/head/conf']), + 'main/loss/bbox_head/loc', 'main/loss/bbox_head/conf', + 'main/loss/mask_head' + ]), trigger=log_interval) trainer.extend(extensions.ProgressBar(update_interval=10)) @@ -198,7 +301,7 @@ def lr_schedule(trainer): trainer.extend( extensions.snapshot_object( model, 'model_iter_{.updater.iteration}'), - trigger=(90000 * 16 / args.batchsize, 'iteration')) + trigger=(n_iteration, 'iteration')) if args.resume: serializers.load_npz(args.resume, trainer, strict=False) diff --git a/examples/mask_rcnn/train_multi.py b/examples/mask_rcnn/train_multi.py deleted file mode 100644 index 921b1e53dc..0000000000 --- a/examples/mask_rcnn/train_multi.py +++ /dev/null @@ -1,275 +0,0 @@ -import argparse -import multiprocessing -import numpy as np -import PIL - -import chainer -import chainer.functions as F -import chainer.links as L -from chainer.optimizer_hooks import WeightDecay -from chainer import serializers -from chainer import training -from chainer.training import extensions - -import chainermn - -from chainercv.chainer_experimental.datasets.sliceable import TransformDataset -from chainercv.chainer_experimental.training.extensions import make_shift -from chainercv.datasets import coco_instance_segmentation_label_names -from chainercv.datasets import COCOInstanceSegmentationDataset -from chainercv.links import MaskRCNNFPNResNet101 -from chainercv.links import MaskRCNNFPNResNet50 -from chainercv.links.model.mask_rcnn.misc import scale_img -from chainercv import transforms - -from chainercv.links.model.fpn import head_loss_post -from chainercv.links.model.fpn import head_loss_pre -from chainercv.links.model.fpn import rpn_loss -from chainercv.links.model.mask_rcnn import mask_loss_post -from chainercv.links.model.mask_rcnn import mask_loss_pre - -# https://docs.chainer.org/en/stable/tips.html#my-training-process-gets-stuck-when-using-multiprocessiterator -try: - import cv2 - cv2.setNumThreads(0) -except ImportError: - pass - - -class TrainChain(chainer.Chain): - - def __init__(self, model): - super(TrainChain, self).__init__() - with self.init_scope(): - self.model = model - - def __call__(self, imgs, masks, labels, bboxes): - B = len(imgs) - pad_size = np.array( - [im.shape[1:] for im in imgs]).max(axis=0) - pad_size = ( - np.ceil( - pad_size / self.model.stride) * self.model.stride).astype(int) - x = np.zeros( - (len(imgs), 3, pad_size[0], pad_size[1]), dtype=np.float32) - for i, img in enumerate(imgs): - _, H, W = img.shape - x[i, :, :H, :W] = img - x = self.xp.array(x) - - # For reducing unnecessary CPU/GPU copy, `masks` is kept in CPU. - pad_masks = [ - np.zeros( - (mask.shape[0], pad_size[0], pad_size[1]), dtype=np.bool) - for mask in masks] - for i, mask in enumerate(masks): - _, H, W = mask.shape - pad_masks[i][:, :H, :W] = mask - masks = pad_masks - - bboxes = [self.xp.array(bbox) for bbox in bboxes] - labels = [self.xp.array(label) for label in labels] - sizes = [img.shape[1:] for img in imgs] - - with chainer.using_config('train', False): - hs = self.model.extractor(x) - - rpn_locs, rpn_confs = self.model.rpn(hs) - anchors = self.model.rpn.anchors(h.shape[2:] for h in hs) - rpn_loc_loss, rpn_conf_loss = rpn_loss( - rpn_locs, rpn_confs, anchors, sizes, bboxes) - - rois, roi_indices = self.model.rpn.decode( - rpn_locs, rpn_confs, anchors, x.shape) - rois = self.xp.vstack([rois] + bboxes) - roi_indices = self.xp.hstack( - [roi_indices] - + [self.xp.array((i,) * len(bbox)) - for i, bbox in enumerate(bboxes)]) - rois, roi_indices = self.model.head.distribute(rois, roi_indices) - rois, roi_indices, head_gt_locs, head_gt_labels = head_loss_pre( - rois, roi_indices, self.model.head.std, bboxes, labels) - head_locs, head_confs = self.model.head(hs, rois, roi_indices) - head_loc_loss, head_conf_loss = head_loss_post( - head_locs, head_confs, - roi_indices, head_gt_locs, head_gt_labels, B) - - mask_rois, mask_roi_indices, gt_segms, gt_mask_labels = mask_loss_pre( - rois, roi_indices, masks, bboxes, - head_gt_labels, self.model.mask_head.segm_size) - n_roi = sum([len(roi) for roi in mask_rois]) - if n_roi > 0: - segms = self.model.mask_head(hs, mask_rois, mask_roi_indices) - mask_loss = mask_loss_post( - segms, mask_roi_indices, gt_segms, gt_mask_labels, B) - else: - # Compute dummy variables to complete the computational graph - mask_rois[0] = self.xp.array([[0, 0, 1, 1]], dtype=np.float32) - mask_roi_indices[0] = self.xp.array([0], dtype=np.int32) - segms = self.model.mask_head(hs, mask_rois, mask_roi_indices) - mask_loss = 0 * F.sum(segms) - loss = (rpn_loc_loss + rpn_conf_loss + - head_loc_loss + head_conf_loss + mask_loss) - chainer.reporter.report({ - 'loss': loss, - 'loss/rpn/loc': rpn_loc_loss, 'loss/rpn/conf': rpn_conf_loss, - 'loss/head/loc': head_loc_loss, 'loss/head/conf': head_conf_loss, - 'loss/mask': mask_loss}, - self) - return loss - - -class Transform(object): - - def __init__(self, min_size, max_size, mean): - self.min_size = min_size - self.max_size = max_size - self.mean = mean - - def __call__(self, in_data): - img, mask, label, bbox = in_data - # Flipping - img, params = transforms.random_flip( - img, x_random=True, return_param=True) - mask = transforms.flip(mask, x_flip=params['x_flip']) - bbox = transforms.flip_bbox( - bbox, img.shape[1:], x_flip=params['x_flip']) - - # Scaling and mean subtraction - img, scale = scale_img( - img, self.min_size, self.max_size) - img -= self.mean - mask = transforms.resize( - mask.astype(np.float32), - img.shape[1:], - interpolation=PIL.Image.NEAREST).astype(np.bool) - bbox = bbox * scale - return img, mask, label, bbox, scale - - -def converter(batch, device=None): - # do not send data to gpu (device is ignored) - return tuple(list(v) for v in zip(*batch)) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument( - '--model', - choices=('mask_rcnn_fpn_resnet50', 'mask_rcnn_fpn_resnet101'), - default='mask_rcnn_fpn_resnet50') - parser.add_argument('--batchsize', type=int, default=16) - parser.add_argument('--iteration', type=int, default=90000) - parser.add_argument('--step', type=int, nargs='*', default=[60000, 80000]) - parser.add_argument('--out', default='result') - parser.add_argument('--resume') - parser.add_argument('--communicator', default='hierarchical') - args = parser.parse_args() - - # https://docs.chainer.org/en/stable/chainermn/tutorial/tips_faqs.html#using-multiprocessiterator - if hasattr(multiprocessing, 'set_start_method'): - multiprocessing.set_start_method('forkserver') - p = multiprocessing.Process() - p.start() - p.join() - - comm = chainermn.create_communicator(args.communicator) - device = comm.intra_rank - - if args.model == 'mask_rcnn_fpn_resnet50': - model = MaskRCNNFPNResNet50( - n_fg_class=len(coco_instance_segmentation_label_names), - pretrained_model='imagenet') - elif args.model == 'mask_rcnn_fpn_resnet101': - model = MaskRCNNFPNResNet101( - n_fg_class=len(coco_instance_segmentation_label_names), - pretrained_model='imagenet') - - model.use_preset('evaluate') - train_chain = TrainChain(model) - chainer.cuda.get_device_from_id(device).use() - train_chain.to_gpu() - - train = TransformDataset( - COCOInstanceSegmentationDataset( - data_dir='/home/yuyu2172/coco', - split='train', return_bbox=True), - ('img', 'mask', 'label', 'bbox'), - Transform(model.min_size, model.max_size, model.extractor.mean)) - - if comm.rank == 0: - indices = np.arange(len(train)) - else: - indices = None - indices = chainermn.scatter_dataset(indices, comm, shuffle=True) - train = train.slice[indices] - - train_iter = chainer.iterators.MultiprocessIterator( - train, args.batchsize // comm.size, - n_processes=args.batchsize // comm.size, - shared_mem=100 * 1000 * 1000 * 4) - - optimizer = chainermn.create_multi_node_optimizer( - chainer.optimizers.MomentumSGD(), comm) - optimizer.setup(train_chain) - optimizer.add_hook(WeightDecay(0.0001)) - - model.extractor.base.conv1.disable_update() - model.extractor.base.res2.disable_update() - for link in model.links(): - if isinstance(link, L.BatchNormalization): - link.disable_update() - - n_iteration = args.iteration * 16 / args.batchsize - updater = training.updaters.StandardUpdater( - train_iter, optimizer, converter=converter, device=device) - trainer = training.Trainer( - updater, (n_iteration, 'iteration'), args.out) - - @make_shift('lr') - def lr_schedule(trainer): - base_lr = 0.02 * args.batchsize / 16 - warm_up_duration = 500 - warm_up_rate = 1 / 3 - - iteration = trainer.updater.iteration - if iteration < warm_up_duration: - rate = warm_up_rate \ - + (1 - warm_up_rate) * iteration / warm_up_duration - else: - rate = 1 - for step in args.step: - if iteration >= step * 16 / args.batchsize: - rate *= 0.1 - - return base_lr * rate - - trainer.extend(lr_schedule) - - if comm.rank == 0: - log_interval = 10, 'iteration' - trainer.extend(extensions.LogReport(trigger=log_interval)) - trainer.extend(extensions.observe_lr(), trigger=log_interval) - trainer.extend(extensions.PrintReport( - ['epoch', 'iteration', 'lr', 'main/loss', - 'main/loss/rpn/loc', 'main/loss/rpn/conf', - 'main/loss/head/loc', 'main/loss/head/conf', - 'main/loss/mask' - ]), - trigger=log_interval) - trainer.extend(extensions.ProgressBar(update_interval=10)) - - trainer.extend(extensions.snapshot(), trigger=(10000, 'iteration')) - trainer.extend( - extensions.snapshot_object( - model, 'model_iter_{.updater.iteration}'), - trigger=(n_iteration, 'iteration')) - - if args.resume: - serializers.load_npz(args.resume, trainer, strict=False) - - trainer.run() - - -if __name__ == '__main__': - main() diff --git a/tests/evaluations_tests/test_eval_semantic_segmentation.py b/tests/evaluations_tests/test_eval_semantic_segmentation.py index a0d626523f..3a4784dff7 100644 --- a/tests/evaluations_tests/test_eval_semantic_segmentation.py +++ b/tests/evaluations_tests/test_eval_semantic_segmentation.py @@ -4,11 +4,10 @@ import numpy as np -from chainer import testing - from chainercv.evaluations import calc_semantic_segmentation_confusion from chainercv.evaluations import calc_semantic_segmentation_iou from chainercv.evaluations import eval_semantic_segmentation +from chainercv.utils import testing @testing.parameterize( diff --git a/tests/experimental_tests/links_tests/model_tests/pspnet_tests/test_pspnet.py b/tests/experimental_tests/links_tests/model_tests/pspnet_tests/test_pspnet.py index 75d0708750..5e184b2fe6 100644 --- a/tests/experimental_tests/links_tests/model_tests/pspnet_tests/test_pspnet.py +++ b/tests/experimental_tests/links_tests/model_tests/pspnet_tests/test_pspnet.py @@ -3,11 +3,11 @@ import chainer from chainer import testing -from chainer.testing import attr from chainercv.experimental.links import PSPNetResNet101 from chainercv.experimental.links import PSPNetResNet50 from chainercv.utils import assert_is_semantic_segmentation_link +from chainercv.utils.testing import attr @testing.parameterize( @@ -33,6 +33,7 @@ def check_call(self): self.assertEqual(y.shape, (2, self.n_class, 120, 160)) @attr.slow + @attr.pfnci_skip def test_call_cpu(self): self.check_call() @@ -43,6 +44,7 @@ def test_call_gpu(self): self.check_call() @attr.slow + @attr.pfnci_skip def test_predict_cpu(self): assert_is_semantic_segmentation_link(self.link, self.n_class) diff --git a/tests/extensions_tests/vis_report_tests/test_detection_vis_report.py b/tests/extensions_tests/vis_report_tests/test_detection_vis_report.py index 287837f650..cd31c5949e 100644 --- a/tests/extensions_tests/vis_report_tests/test_detection_vis_report.py +++ b/tests/extensions_tests/vis_report_tests/test_detection_vis_report.py @@ -8,11 +8,11 @@ import chainer from chainer.datasets import TupleDataset from chainer.iterators import SerialIterator -from chainer import testing from chainer.testing import attr from chainercv.extensions import DetectionVisReport from chainercv.utils import generate_random_bbox +from chainercv.utils import testing try: import matplotlib # NOQA diff --git a/tests/links_tests/connection_tests/test_conv_2d_bn_activ.py b/tests/links_tests/connection_tests/test_conv_2d_bn_activ.py index 47d44d811b..10c0885b83 100644 --- a/tests/links_tests/connection_tests/test_conv_2d_bn_activ.py +++ b/tests/links_tests/connection_tests/test_conv_2d_bn_activ.py @@ -6,24 +6,16 @@ from chainer.backends import cuda from chainer.functions import relu from chainer import testing -from chainer.testing import attr +from chainermn import create_communicator from chainercv.links import Conv2DBNActiv - -from chainermn import create_communicator +from chainercv.utils.testing import attr def _add_one(x): return x + 1 -try: - import mpi4py.MPI # NOQA - _available = True -except ImportError: - _available = False - - @testing.parameterize(*testing.product({ 'dilate': [1, 2], 'args_style': ['explicit', 'None', 'omit'], @@ -128,7 +120,7 @@ def test_backward_gpu(self): self.check_backward(cuda.to_gpu(self.x), cuda.to_gpu(self.gy)) -@unittest.skipUnless(_available, 'mpi4py is not installed') +@attr.mpi class TestConv2DMultiNodeBNActiv(unittest.TestCase): in_channels = 1 diff --git a/tests/links_tests/model_tests/deeplab_tests/test_deeplab_v3_plus.py b/tests/links_tests/model_tests/deeplab_tests/test_deeplab_v3_plus.py index 887d0a6d25..172238337d 100644 --- a/tests/links_tests/model_tests/deeplab_tests/test_deeplab_v3_plus.py +++ b/tests/links_tests/model_tests/deeplab_tests/test_deeplab_v3_plus.py @@ -51,4 +51,32 @@ def test_predict_gpu(self): assert_is_semantic_segmentation_link(self.link, self.n_class) +@testing.parameterize(*testing.product({ + 'model': [DeepLabV3plusXception65], + 'pretrained_model': ['cityscapes', 'ade20k', 'voc'], + 'n_class': [None, 19, 150, 21], +})) +class TestDeepLabV3plusXception65Pretrained(unittest.TestCase): + + @attr.slow + def test_pretrained(self): + kwargs = { + 'n_class': self.n_class, + 'pretrained_model': self.pretrained_model, + } + + if self.pretrained_model == 'cityscapes': + valid = self.n_class in {None, 19} + elif self.pretrained_model == 'ade20k': + valid = self.n_class in {None, 150} + elif self.pretrained_model == 'voc': + valid = self.n_class in {None, 21} + + if valid: + self.model(**kwargs) + else: + with self.assertRaises(ValueError): + self.model(**kwargs) + + testing.run_module(__name__, __file__) diff --git a/tests/links_tests/model_tests/fpn_tests/test_faster_rcnn.py b/tests/links_tests/model_tests/fpn_tests/test_faster_rcnn.py index b565062a8d..bebfa4a79b 100644 --- a/tests/links_tests/model_tests/fpn_tests/test_faster_rcnn.py +++ b/tests/links_tests/model_tests/fpn_tests/test_faster_rcnn.py @@ -7,10 +7,13 @@ from chainer import testing from chainer.testing import attr +from chainercv.links.model.fpn import BboxHead from chainercv.links.model.fpn import FasterRCNN -from chainercv.links.model.fpn import Head +from chainercv.links.model.fpn import MaskHead from chainercv.links.model.fpn import RPN +from chainercv.utils import assert_is_bbox from chainercv.utils import assert_is_detection_link +from chainercv.utils import assert_is_instance_segmentation_link def _random_array(xp, shape): @@ -31,24 +34,55 @@ def __call__(self, x): class DummyFasterRCNN(FasterRCNN): - def __init__(self, n_fg_class): + def __init__(self, n_fg_class, return_values, min_size, max_size): extractor = DummyExtractor() super(DummyFasterRCNN, self).__init__( extractor=extractor, rpn=RPN(extractor.scales), - head=Head(n_fg_class + 1, extractor.scales), + bbox_head=BboxHead(n_fg_class + 1, extractor.scales), + mask_head=MaskHead(n_fg_class + 1, extractor.scales), + return_values=return_values, + min_size=min_size, max_size=max_size, ) -@testing.parameterize( - {'n_fg_class': 1}, - {'n_fg_class': 5}, - {'n_fg_class': 20}, -) +@testing.parameterize(*testing.product_dict( + [ + {'return_values': 'detection'}, + {'return_values': 'instance_segmentation'}, + {'return_values': 'rpn'} + ], + [ + {'n_fg_class': 1}, + {'n_fg_class': 5}, + {'n_fg_class': 20}, + ], + [ + # { + # 'in_sizes': [(480, 640), (320, 320)], + # 'min_size': 800, 'max_size': 1333, + # 'expected_shape': (800, 1088), + # }, + { + 'in_sizes': [(200, 50), (400, 100)], + 'min_size': 200, 'max_size': 320, + 'expected_shape': (320, 96), + }, + ], +)) class TestFasterRCNN(unittest.TestCase): def setUp(self): - self.link = DummyFasterRCNN(n_fg_class=self.n_fg_class) + if self.return_values == 'detection': + return_values = ['bboxes', 'labels', 'scores'] + elif self.return_values == 'instance_segmentation': + return_values = ['masks', 'labels', 'scores'] + elif self.return_values == 'rpn': + return_values = ['rois'] + self.link = DummyFasterRCNN(n_fg_class=self.n_fg_class, + return_values=return_values, + min_size=self.min_size, + max_size=self.max_size) def test_use_preset(self): self.link.nms_thresh = 0 @@ -71,29 +105,20 @@ def test_use_preset(self): def _check_call(self): x = _random_array(self.link.xp, (2, 3, 32, 32)) with chainer.using_config('train', False): - rois, roi_indices, head_locs, head_confs = self.link(x) + hs, rois, roi_indices = self.link(x) - self.assertEqual(len(rois), len(self.link.extractor.scales)) - self.assertEqual(len(roi_indices), len(self.link.extractor.scales)) + self.assertEqual(len(hs), len(self.link.extractor.scales)) for l in range(len(self.link.extractor.scales)): - self.assertIsInstance(rois[l], self.link.xp.ndarray) - self.assertEqual(rois[l].shape[1:], (4,)) + self.assertIsInstance(hs[l], chainer.Variable) + self.assertIsInstance(hs[l].data, self.link.xp.ndarray) - self.assertIsInstance(roi_indices[l], self.link.xp.ndarray) - self.assertEqual(roi_indices[l].shape[1:], ()) + self.assertIsInstance(rois, self.link.xp.ndarray) + self.assertEqual(rois.shape[1:], (4,)) - self.assertEqual(rois[l].shape[0], roi_indices[l].shape[0]) + self.assertIsInstance(roi_indices, self.link.xp.ndarray) + self.assertEqual(roi_indices.shape[1:], ()) - n_roi = sum( - len(rois[l]) for l in range(len(self.link.extractor.scales))) - - self.assertIsInstance(head_locs, chainer.Variable) - self.assertIsInstance(head_locs.array, self.link.xp.ndarray) - self.assertEqual(head_locs.shape, (n_roi, self.n_fg_class + 1, 4)) - - self.assertIsInstance(head_confs, chainer.Variable) - self.assertIsInstance(head_confs.array, self.link.xp.ndarray) - self.assertEqual(head_confs.shape, (n_roi, self.n_fg_class + 1)) + self.assertEqual(rois.shape[0], roi_indices.shape[0]) def test_call_cpu(self): self._check_call() @@ -109,21 +134,41 @@ def test_call_train_mode(self): with chainer.using_config('train', True): self.link(x) + def _check_predict(self): + if self.return_values == 'detection': + assert_is_detection_link(self.link, self.n_fg_class) + elif self.return_values == 'instance_segmentation': + assert_is_instance_segmentation_link(self.link, self.n_fg_class) + elif self.return_values == 'rpn': + imgs = [ + np.random.randint( + 0, 256, size=(3, 480, 320)).astype(np.float32), + np.random.randint( + 0, 256, size=(3, 480, 320)).astype(np.float32)] + result = self.link.predict(imgs) + assert len(result) == 1 + assert len(result[0]) == 1 + for i in range(len(result[0])): + roi = result[0][i] + assert_is_bbox(roi) + + @attr.slow def test_predict_cpu(self): - assert_is_detection_link(self.link, self.n_fg_class) + self._check_predict() @attr.gpu def test_predict_gpu(self): self.link.to_gpu() - assert_is_detection_link(self.link, self.n_fg_class) + self._check_predict() def test_prepare(self): - imgs = [ - np.random.randint(0, 255, size=(3, 480, 640)).astype(np.float32), - np.random.randint(0, 255, size=(3, 320, 320)).astype(np.float32), - ] - x, scales = self.link.prepare(imgs) - self.assertEqual(x.shape, (2, 3, 800, 1088)) + imgs = [_random_array(np, (3, s[0], s[1])) for s in self.in_sizes] + out, scales = self.link.prepare(imgs) + self.assertIsInstance(out, np.ndarray) + full_expected_shape = (len(self.in_sizes), 3, + self.expected_shape[0], + self.expected_shape[1]) + self.assertEqual(out.shape, full_expected_shape) testing.run_module(__name__, __file__) diff --git a/tests/links_tests/model_tests/fpn_tests/test_faster_rcnn_fpn_resnet.py b/tests/links_tests/model_tests/fpn_tests/test_faster_rcnn_fpn_resnet.py index 071b1f979c..cf5537ed3e 100644 --- a/tests/links_tests/model_tests/fpn_tests/test_faster_rcnn_fpn_resnet.py +++ b/tests/links_tests/model_tests/fpn_tests/test_faster_rcnn_fpn_resnet.py @@ -3,10 +3,10 @@ import chainer from chainer import testing -from chainer.testing import attr from chainercv.links import FasterRCNNFPNResNet101 from chainercv.links import FasterRCNNFPNResNet50 +from chainercv.utils.testing import attr @testing.parameterize(*testing.product({ @@ -28,6 +28,7 @@ def _check_call(self): self.link(self.link.xp.array(x)) @attr.slow + @attr.pfnci_skip def test_call_cpu(self): self._check_call() diff --git a/tests/links_tests/model_tests/mask_rcnn_tests/test_mask_head.py b/tests/links_tests/model_tests/fpn_tests/test_mask_head.py similarity index 97% rename from tests/links_tests/model_tests/mask_rcnn_tests/test_mask_head.py rename to tests/links_tests/model_tests/fpn_tests/test_mask_head.py index 7f63c5eda1..5110af26a6 100644 --- a/tests/links_tests/model_tests/mask_rcnn_tests/test_mask_head.py +++ b/tests/links_tests/model_tests/fpn_tests/test_mask_head.py @@ -7,9 +7,9 @@ from chainer import testing from chainer.testing import attr -from chainercv.links.model.mask_rcnn import MaskHead -from chainercv.links.model.mask_rcnn import mask_loss_post -from chainercv.links.model.mask_rcnn import mask_loss_pre +from chainercv.links.model.fpn import MaskHead +from chainercv.links.model.fpn import mask_loss_post +from chainercv.links.model.fpn import mask_loss_pre from chainercv.utils import mask_to_bbox diff --git a/tests/links_tests/model_tests/mask_rcnn_tests/test_misc.py b/tests/links_tests/model_tests/fpn_tests/test_mask_utils.py similarity index 92% rename from tests/links_tests/model_tests/mask_rcnn_tests/test_misc.py rename to tests/links_tests/model_tests/fpn_tests/test_mask_utils.py index 6bd6722c7a..5ae85bf237 100644 --- a/tests/links_tests/model_tests/mask_rcnn_tests/test_misc.py +++ b/tests/links_tests/model_tests/fpn_tests/test_mask_utils.py @@ -5,8 +5,8 @@ from chainer import testing -from chainercv.links.model.mask_rcnn.misc import segm_to_mask -from chainercv.links.model.mask_rcnn.misc import mask_to_segm +from chainercv.links.model.fpn.mask_utils import segm_to_mask +from chainercv.links.model.fpn.mask_utils import mask_to_segm class TestSegmToMask(unittest.TestCase): diff --git a/tests/links_tests/model_tests/mask_rcnn_tests/test_mask_rcnn.py b/tests/links_tests/model_tests/mask_rcnn_tests/test_mask_rcnn.py deleted file mode 100644 index 637bab61c4..0000000000 --- a/tests/links_tests/model_tests/mask_rcnn_tests/test_mask_rcnn.py +++ /dev/null @@ -1,132 +0,0 @@ -from __future__ import division - -import numpy as np -import unittest - -import chainer -from chainer import testing -from chainer.testing import attr - -from chainercv.links.model.fpn import Head -from chainercv.links.model.fpn import RPN -from chainercv.links.model.mask_rcnn import MaskRCNN -from chainercv.links.model.mask_rcnn import MaskHead -from chainercv.utils import assert_is_instance_segmentation_link - - -def _random_array(xp, shape): - return xp.array( - np.random.uniform(-1, 1, size=shape), dtype=np.float32) - - -class DummyExtractor(chainer.Link): - scales = (1 / 2, 1 / 4, 1 / 8) - mean = _random_array(np, (3, 1, 1)) - n_channel = 16 - - def __call__(self, x): - n, _, h, w = x.shape - return [chainer.Variable(_random_array( - self.xp, (n, self.n_channel, int(h * scale), int(w * scale)))) - for scale in self.scales] - - -class DummyMaskRCNN(MaskRCNN): - - def __init__(self, n_fg_class): - extractor = DummyExtractor() - n_class = n_fg_class + 1 - super(DummyMaskRCNN, self).__init__( - extractor=extractor, - rpn=RPN(extractor.scales), - head=Head(n_class, extractor.scales), - mask_head=MaskHead(n_class, extractor.scales) - ) - - -@testing.parameterize( - {'n_fg_class': 1}, - {'n_fg_class': 5}, - {'n_fg_class': 20}, -) -class TestMaskRCNN(unittest.TestCase): - - def setUp(self): - self.link = DummyMaskRCNN(n_fg_class=self.n_fg_class) - - def test_use_preset(self): - self.link.nms_thresh = 0 - self.link.score_thresh = 0 - - self.link.use_preset('visualize') - self.assertEqual(self.link.nms_thresh, 0.5) - self.assertEqual(self.link.score_thresh, 0.7) - - self.link.nms_thresh = 0 - self.link.score_thresh = 0 - - self.link.use_preset('evaluate') - self.assertEqual(self.link.nms_thresh, 0.5) - self.assertEqual(self.link.score_thresh, 0.05) - - with self.assertRaises(ValueError): - self.link.use_preset('unknown') - - def _check_call(self): - B = 2 - size = 32 - x = _random_array(self.link.xp, (B, 3, size, size)) - with chainer.using_config('train', False): - hs, rois, roi_indices = self.link(x) - - self.assertEqual(len(hs), len(self.link.extractor.scales)) - self.assertEqual(len(rois), len(self.link.extractor.scales)) - self.assertEqual(len(roi_indices), len(self.link.extractor.scales)) - for l, scale in enumerate(self.link.extractor.scales): - self.assertIsInstance(rois[l], self.link.xp.ndarray) - self.assertEqual(rois[l].shape[1:], (4,)) - - self.assertIsInstance(roi_indices[l], self.link.xp.ndarray) - self.assertEqual(roi_indices[l].shape[1:], ()) - - self.assertEqual(rois[l].shape[0], roi_indices[l].shape[0]) - - self.assertIsInstance(hs[l], chainer.Variable) - self.assertIsInstance(hs[l].array, self.link.xp.ndarray) - feat_size = int(size * scale) - self.assertEqual( - hs[l].shape, - (B, self.link.extractor.n_channel, feat_size, feat_size)) - - def test_call_cpu(self): - self._check_call() - - @attr.gpu - def test_call_gpu(self): - self.link.to_gpu() - self._check_call() - - def test_call_train_mode(self): - x = _random_array(self.link.xp, (2, 3, 32, 32)) - with self.assertRaises(AssertionError): - with chainer.using_config('train', True): - self.link(x) - - def test_predict_cpu(self): - assert_is_instance_segmentation_link(self.link, self.n_fg_class) - - @attr.gpu - def test_predict_gpu(self): - self.link.to_gpu() - assert_is_instance_segmentation_link(self.link, self.n_fg_class) - - def test_prepare(self): - imgs = [ - np.random.randint(0, 255, size=(3, 480, 640)).astype(np.float32), - np.random.randint(0, 255, size=(3, 320, 320)).astype(np.float32), - ] - x, _, _ = self.link.prepare(imgs) - self.assertEqual(x.shape, (2, 3, 800, 1088)) - - -testing.run_module(__name__, __file__) diff --git a/tests/links_tests/model_tests/resnet_tests/test_resnet.py b/tests/links_tests/model_tests/resnet_tests/test_resnet.py index aba6db6c75..94abf5545a 100644 --- a/tests/links_tests/model_tests/resnet_tests/test_resnet.py +++ b/tests/links_tests/model_tests/resnet_tests/test_resnet.py @@ -2,13 +2,13 @@ import numpy as np -from chainer import testing from chainer.testing import attr from chainer import Variable from chainercv.links import ResNet101 from chainercv.links import ResNet152 from chainercv.links import ResNet50 +from chainercv.utils import testing @testing.parameterize(*( diff --git a/tests/links_tests/model_tests/senet_tests/test_se_resnet.py b/tests/links_tests/model_tests/senet_tests/test_se_resnet.py index ab5f2fe509..b0e4d6f22d 100644 --- a/tests/links_tests/model_tests/senet_tests/test_se_resnet.py +++ b/tests/links_tests/model_tests/senet_tests/test_se_resnet.py @@ -2,13 +2,13 @@ import numpy as np -from chainer import testing from chainer.testing import attr from chainer import Variable from chainercv.links import SEResNet101 from chainercv.links import SEResNet152 from chainercv.links import SEResNet50 +from chainercv.utils import testing @testing.parameterize(*( diff --git a/tests/links_tests/model_tests/senet_tests/test_se_resnext.py b/tests/links_tests/model_tests/senet_tests/test_se_resnext.py index 4b45015d32..e5d7ae5022 100644 --- a/tests/links_tests/model_tests/senet_tests/test_se_resnext.py +++ b/tests/links_tests/model_tests/senet_tests/test_se_resnext.py @@ -2,12 +2,12 @@ import numpy as np -from chainer import testing from chainer.testing import attr from chainer import Variable from chainercv.links import SEResNeXt101 from chainercv.links import SEResNeXt50 +from chainercv.utils import testing @testing.parameterize(*( diff --git a/tests/links_tests/model_tests/ssd_tests/test_multibox_loss.py b/tests/links_tests/model_tests/ssd_tests/test_multibox_loss.py index 5d5e8048f9..1d70ae9b06 100644 --- a/tests/links_tests/model_tests/ssd_tests/test_multibox_loss.py +++ b/tests/links_tests/model_tests/ssd_tests/test_multibox_loss.py @@ -8,18 +8,10 @@ from chainer.backends import cuda import chainer.functions as F from chainer import testing -from chainer.testing import attr - -from chainercv.links.model.ssd import multibox_loss - from chainermn import create_communicator - -try: - import mpi4py.MPI # NOQA - _available = True -except ImportError: - _available = False +from chainercv.links.model.ssd import multibox_loss +from chainercv.utils.testing import attr @testing.parameterize(*testing.product({ @@ -131,7 +123,7 @@ def test_forward_gpu(self): self.k) -@unittest.skipUnless(_available, 'mpi4py is not installed') +@attr.mpi class TestMultiNodeMultiboxLoss(unittest.TestCase): k = 3 diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index 07c450031b..2a0379eed5 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -4,11 +4,11 @@ import chainer from chainer.initializers import Zero -from chainer import testing from chainer.testing import attr from chainer import Variable from chainercv.links import VGG16 +from chainercv.utils import testing @testing.parameterize( diff --git a/tests/transforms_tests/image_tests/test_random_expand.py b/tests/transforms_tests/image_tests/test_random_expand.py index 6b3c731ade..800ed9663b 100644 --- a/tests/transforms_tests/image_tests/test_random_expand.py +++ b/tests/transforms_tests/image_tests/test_random_expand.py @@ -2,8 +2,8 @@ import numpy as np -from chainer import testing from chainercv.transforms import random_expand +from chainercv.utils import testing @testing.parameterize( diff --git a/tests/transforms_tests/image_tests/test_resize_contain.py b/tests/transforms_tests/image_tests/test_resize_contain.py index 006ab0ddcf..2ce0f7b79a 100644 --- a/tests/transforms_tests/image_tests/test_resize_contain.py +++ b/tests/transforms_tests/image_tests/test_resize_contain.py @@ -3,8 +3,8 @@ import numpy as np import PIL -from chainer import testing from chainercv.transforms import resize_contain +from chainercv.utils import testing @testing.parameterize(*testing.product_dict( @@ -34,7 +34,7 @@ class TestResizeContain(unittest.TestCase): def test_resize_contain(self): H, W = 32, 64 - img = np.random.uniform(255, size=(3, H, W)) + img = np.random.uniform(255, size=(3, H, W))\ out, param = resize_contain( img, self.size, fill=self.fill, diff --git a/tests/utils_tests/image_tests/test_tile_images.py b/tests/utils_tests/image_tests/test_tile_images.py index 3a7c22770e..3a56eb329c 100644 --- a/tests/utils_tests/image_tests/test_tile_images.py +++ b/tests/utils_tests/image_tests/test_tile_images.py @@ -3,8 +3,7 @@ import numpy as np import unittest -from chainer import testing - +from chainercv.utils import testing from chainercv.utils import tile_images diff --git a/tests/utils_tests/iterator_tests/test_apply_to_iterator.py b/tests/utils_tests/iterator_tests/test_apply_to_iterator.py index bba5d2f54c..f946cdad0d 100644 --- a/tests/utils_tests/iterator_tests/test_apply_to_iterator.py +++ b/tests/utils_tests/iterator_tests/test_apply_to_iterator.py @@ -8,12 +8,7 @@ from chainermn import create_communicator from chainercv.utils import apply_to_iterator - -try: - import mpi4py.MPI # NOQA - _available = True -except ImportError: - _available = False +from chainercv.utils.testing import attr @testing.parameterize(*testing.product({ @@ -129,7 +124,7 @@ def _check_apply_to_iterator(self, comm=None): def test_apply_to_iterator(self): self._check_apply_to_iterator() - @unittest.skipUnless(_available, 'mpi4py is not installed') + @attr.mpi def test_apply_to_iterator_with_comm(self): comm = create_communicator('naive') self._check_apply_to_iterator(comm) diff --git a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_bbox.py b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_bbox.py index a107d749ec..f4c5a52e7d 100644 --- a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_bbox.py +++ b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_bbox.py @@ -1,9 +1,8 @@ import numpy as np import unittest -from chainer import testing - from chainercv.utils import assert_is_bbox +from chainercv.utils import testing @testing.parameterize( diff --git a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_bbox_dataset.py b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_bbox_dataset.py index f78d23bb70..2e6d2f5136 100644 --- a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_bbox_dataset.py +++ b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_bbox_dataset.py @@ -2,10 +2,10 @@ import unittest from chainer.dataset import DatasetMixin -from chainer import testing from chainercv.utils import assert_is_bbox_dataset from chainercv.utils import generate_random_bbox +from chainercv.utils import testing class BboxDataset(DatasetMixin): diff --git a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_detection_link.py b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_detection_link.py index 0d682fef1c..3075741bfa 100644 --- a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_detection_link.py +++ b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_detection_link.py @@ -2,10 +2,10 @@ import unittest import chainer -from chainer import testing from chainercv.utils import assert_is_detection_link from chainercv.utils import generate_random_bbox +from chainercv.utils import testing class DetectionLink(chainer.Link): diff --git a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_image.py b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_image.py index 9fc73f216c..5dc6adfbea 100644 --- a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_image.py +++ b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_image.py @@ -1,9 +1,8 @@ import numpy as np import unittest -from chainer import testing - from chainercv.utils import assert_is_image +from chainercv.utils import testing @testing.parameterize( diff --git a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_instance_segmentation_dataset.py b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_instance_segmentation_dataset.py index 4b5120aea5..b87932e43b 100644 --- a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_instance_segmentation_dataset.py +++ b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_instance_segmentation_dataset.py @@ -2,9 +2,9 @@ import unittest from chainer.dataset import DatasetMixin -from chainer import testing from chainercv.utils import assert_is_instance_segmentation_dataset +from chainercv.utils import testing class InstanceSegmentationDataset(DatasetMixin): diff --git a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_point.py b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_point.py index fc9e6195f1..57e04e7fbc 100644 --- a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_point.py +++ b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_point.py @@ -1,9 +1,9 @@ import numpy as np import unittest -from chainer import testing from chainercv.utils import assert_is_point +from chainercv.utils import testing def _random_visible_including_true(n): @@ -16,59 +16,59 @@ def _random_visible_including_true(n): @testing.parameterize( # no visible and size {'point': np.random.uniform(-1, 1, size=(1, 10, 2)).astype(np.float32), - 'no_error': True}, + 'valid': True}, {'point': [((1., 2.), (4., 8.))], - 'no_error': False}, + 'valid': False}, {'point': np.random.uniform(-1, 1, size=(1, 10, 2)).astype(np.int32), - 'no_error': False}, + 'valid': False}, {'point': np.random.uniform(-1, 1, size=(1, 10, 3)).astype(np.float32), - 'no_error': False}, + 'valid': False}, # use visible, no size {'point': np.random.uniform(-1, 1, size=(1, 10, 2)).astype(np.float32), 'visible': np.random.randint(0, 2, size=(1, 10,)).astype(np.bool), - 'no_error': True}, + 'valid': True}, {'point': np.random.uniform(-1, 1, size=(1, 4, 2)).astype(np.float32), 'visible': [(True, True, True, True)], - 'no_error': False}, + 'valid': False}, {'point': np.random.uniform(-1, 1, size=(1, 10, 2)).astype(np.float32), 'visible': np.random.randint(0, 2, size=(1, 10,)).astype(np.int32), - 'no_error': False}, + 'valid': False}, {'point': np.random.uniform(-1, 1, size=(1, 10, 2)).astype(np.float32), 'visible': np.random.randint(0, 2, size=(1, 10, 2)).astype(np.bool), - 'no_error': False}, + 'valid': False}, {'point': np.random.uniform(-1, 1, size=(1, 10, 2)).astype(np.float32), 'visible': np.random.randint(0, 2, size=(1, 9,)).astype(np.bool), - 'no_error': False}, + 'valid': False}, # no visible, use size {'point': np.random.uniform(0, 32, size=(1, 10, 2)).astype(np.float32), 'size': (32, 32), - 'no_error': True}, + 'valid': True}, {'point': np.random.uniform(32, 64, size=(1, 10, 2)).astype(np.float32), 'size': (32, 32), - 'no_error': False}, + 'valid': False}, # use visible and size {'point': np.random.uniform(0, 32, size=(1, 10, 2)).astype(np.float32), 'visible': np.random.randint(0, 2, size=(1, 10,)).astype(np.bool), 'size': (32, 32), - 'no_error': True}, + 'valid': True}, {'point': np.random.uniform(32, 64, size=(1, 10, 2)).astype(np.float32), 'visible': [_random_visible_including_true(10)], 'size': (32, 32), - 'no_error': False}, + 'valid': False}, # check n_point {'point': np.random.uniform(-1, 1, size=(1, 10, 2)).astype(np.float32), 'visible': np.random.randint(0, 2, size=(1, 10,)).astype(np.bool), 'n_point': 10, - 'no_error': True}, + 'valid': True}, {'point': np.random.uniform(-1, 1, size=(1, 10, 2)).astype(np.float32), 'visible': np.random.randint(0, 2, size=(1, 10,)).astype(np.bool), 'n_point': 11, - 'no_error': False, + 'valid': False, }, # check different instance size {'point': np.random.uniform(-1, 1, size=(1, 10, 2)).astype(np.float32), 'visible': np.random.randint(0, 2, size=(2, 10,)).astype(np.bool), - 'no_error': False}, + 'valid': False}, ) class TestAssertIsPoint(unittest.TestCase): @@ -81,7 +81,7 @@ def setUp(self): self.n_point = None def test_assert_is_point(self): - if self.no_error: + if self.valid: assert_is_point( self.point, self.visible, self.size, self.n_point) else: diff --git a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_point_dataset.py b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_point_dataset.py index bdfbcfc081..cd7dc11a74 100644 --- a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_point_dataset.py +++ b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_point_dataset.py @@ -2,9 +2,9 @@ import unittest from chainer.dataset import DatasetMixin -from chainer import testing from chainercv.utils import assert_is_point_dataset +from chainercv.utils import testing class PointDataset(DatasetMixin): diff --git a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_semantic_segmentation_dataset.py b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_semantic_segmentation_dataset.py index 2fb1827a46..fada575510 100644 --- a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_semantic_segmentation_dataset.py +++ b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_semantic_segmentation_dataset.py @@ -2,9 +2,9 @@ import unittest from chainer.dataset import DatasetMixin -from chainer import testing from chainercv.utils import assert_is_semantic_segmentation_dataset +from chainercv.utils import testing class SemanticSegmentationDataset(DatasetMixin): diff --git a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_semantic_segmentation_link.py b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_semantic_segmentation_link.py index fe7b4b3403..6de3b4c5fe 100644 --- a/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_semantic_segmentation_link.py +++ b/tests/utils_tests/testing_tests/assertions_tests/test_assert_is_semantic_segmentation_link.py @@ -2,9 +2,9 @@ import unittest import chainer -from chainer import testing from chainercv.utils import assert_is_semantic_segmentation_link +from chainercv.utils import testing class SemanticSegmentationLink(chainer.Link): diff --git a/tests/visualizations_tests/test_vis_image.py b/tests/visualizations_tests/test_vis_image.py index b549ebf487..21d371f41c 100644 --- a/tests/visualizations_tests/test_vis_image.py +++ b/tests/visualizations_tests/test_vis_image.py @@ -2,8 +2,7 @@ import numpy as np -from chainer import testing - +from chainercv.utils import testing from chainercv.visualizations import vis_image try: