def __init__(self,
                 prototxt,
                 weights,
                 inputs,
                 outputs,
                 volume_specs=None,
                 use_gpu=None):

        for f in [prototxt, weights]:
            if not os.path.isfile(f):
                raise RuntimeError("%s does not exist" % f)
        self.prototxt = prototxt
        self.weights = weights
        self.inputs = inputs
        self.outputs = outputs

        if use_gpu is not None:

            logger.debug("Predict process: using GPU %d" % use_gpu)
            caffe.enumerate_devices(False)
            caffe.set_devices((use_gpu, ))
            caffe.set_mode_gpu()
            caffe.select_device(use_gpu, False)

        self.net = caffe.Net(self.prototxt, self.weights, caffe.TEST)
        self.net_io = NetIoWrapper(self.net, self.outputs.values())
Exemple #2
0
    def start(self):

        logger.info("Initializing solver...")

        if self.use_gpu is not None:

            logger.debug("Predict process: using GPU %d" % self.use_gpu)
            caffe.enumerate_devices(False)
            caffe.set_devices((self.use_gpu, ))
            caffe.set_mode_gpu()
            caffe.select_device(self.use_gpu, False)

        self.net = caffe.Net(self.prototxt, self.weights, caffe.TEST)
        self.net_io = NetIoWrapper(self.net, self.outputs.keys())
Exemple #3
0
class CaffePredict(object):
    '''Augments a batch with network predictions.

    Args:

        prototxt (string): Filename of the network prototxt.

        weights (string): Filename of the network weights.

        input_key (string): Name of the input layer of the network.

        output_key (string): Name of the output layer of the network.

        gpu (int): Which GPU to use.
    '''
    def __init__(self, prototxt, weights, input_key, output_key, gpu):

        # TODO validate that gpu is available
        assert os.path.exists(prototxt)
        assert os.path.exists(weights)
        for f in [prototxt, weights]:
            if not os.path.isfile(f):
                raise RuntimeError("%s does not exist" % f)
        self.prototxt = prototxt
        self.weights = weights
        self.input_key = input_key
        self.output_key = output_key

        caffe.enumerate_devices(False)
        caffe.set_devices((gpu, ))
        caffe.set_mode_gpu()
        caffe.select_device(gpu, False)

        self.net = caffe.Net(self.prototxt, self.weights, caffe.TEST)
        self.net_io = NetIoWrapper(self.net, [self.output_key])

    def __call__(self, input_data):
        assert isinstance(input_data, np.ndarray)
        self.net_io.set_inputs({self.input_key: input_data})

        self.net.forward()
        output = self.net_io.get_outputs()[self.output_key]
        assert isinstance(output, np.ndarray)
        # remove batch-dimension
        if output.ndim == 5:
            output = output[0]
        assert output.ndim == 4
        return output.astype('float32')
Exemple #4
0
    def __predict(self, use_gpu):

        if not self.net_initialized:

            logger.info("Initializing solver...")

            if use_gpu is not None:

                logger.debug("Predict process: using GPU %d" % use_gpu)
                caffe.enumerate_devices(False)
                caffe.set_devices((use_gpu, ))
                caffe.set_mode_gpu()
                caffe.select_device(use_gpu, False)

            self.net = caffe.Net(self.prototxt, self.weights, caffe.TEST)
            self.net_io = NetIoWrapper(self.net)
            self.net_initialized = True

        start = time.time()

        batch = self.batch_in.get()

        fetch_time = time.time() - start

        self.net_io.set_inputs({
            'data':
            batch.volumes[VolumeTypes.RAW].data[np.newaxis, np.newaxis, :],
        })
        self.net.forward()
        output = self.net_io.get_outputs()

        predict_time = time.time() - start

        logger.info(
            "Predict process: time=%f (including %f waiting for batch)" %
            (predict_time, fetch_time))

        assert len(
            output['aff_pred'].shape
        ) == 5, "Got affinity prediction with unexpected number of dimensions, should be 1 (direction) + 3 (spatial) + 1 (batch, not used), but is %d" % len(
            output['aff_pred'].shape)
        batch.volumes[VolumeTypes.PRED_AFFINITIES] = Volume(
            output['aff_pred'][0], Roi(), (1, 1, 1))

        return batch
Exemple #5
0
    def start(self):

        logger.info("Initializing solver...")

        if self.use_gpu is not None:

            logger.debug("Train process: using GPU %d", self.use_gpu)
            caffe.enumerate_devices(False)
            caffe.set_devices((self.use_gpu, ))
            caffe.set_mode_gpu()
            caffe.select_device(self.use_gpu, False)

        self.solver = caffe.get_solver(self.solver_parameters)
        if self.solver_parameters.resume_from is not None:
            logger.debug("Train process: restoring solver state from %s",
                         self.solver_parameters.resume_from)
            self.solver.restore(self.solver_parameters.resume_from)

        names_net_outputs = self.outputs.keys() + self.gradients.keys()
        self.net_io = NetIoWrapper(self.solver.net, names_net_outputs)
Exemple #6
0
    def __init__(self, prototxt, weights, input_key, output_key, gpu):

        # TODO validate that gpu is available
        assert os.path.exists(prototxt)
        assert os.path.exists(weights)
        for f in [prototxt, weights]:
            if not os.path.isfile(f):
                raise RuntimeError("%s does not exist" % f)
        self.prototxt = prototxt
        self.weights = weights
        self.input_key = input_key
        self.output_key = output_key

        caffe.enumerate_devices(False)
        caffe.set_devices((gpu, ))
        caffe.set_mode_gpu()
        caffe.select_device(gpu, False)

        self.net = caffe.Net(self.prototxt, self.weights, caffe.TEST)
        self.net_io = NetIoWrapper(self.net, [self.output_key])
Exemple #7
0
    def __train(self, use_gpu):

        start = time.time()

        if not self.solver_initialized:

            logger.info("Initializing solver...")

            if use_gpu is not None:

                logger.debug("Train process: using GPU %d" % use_gpu)
                caffe.enumerate_devices(False)
                caffe.set_devices((use_gpu, ))
                caffe.set_mode_gpu()
                caffe.select_device(use_gpu, False)

            self.solver = caffe.get_solver(self.solver_parameters)
            if self.solver_parameters.resume_from is not None:
                logger.debug("Train process: restoring solver state from " +
                             self.solver_parameters.resume_from)
                self.solver.restore(self.solver_parameters.resume_from)

            self.net_io = NetIoWrapper(self.solver.net)

            self.solver_initialized = True

        batch, request = self.batch_in.get()

        data = {
            'data':
            batch.volumes[VolumeType.RAW].data[np.newaxis, np.newaxis, :],
            'aff_label':
            batch.volumes[VolumeType.GT_AFFINITIES].data[np.newaxis, :],
        }

        if self.solver_parameters.train_state.get_stage(0) == 'euclid':
            logger.debug(
                "Train process: preparing input data for Euclidean training")
            self.__prepare_euclidean(batch, data)
        else:
            logger.debug(
                "Train process: preparing input data for Malis training")
            self.__prepare_malis(batch, data)

        self.net_io.set_inputs(data)

        loss = self.solver.step(1)
        # self.__consistency_check()
        output = self.net_io.get_outputs()
        batch.volumes[VolumeType.PRED_AFFINITIES] = Volume(
            output['aff_pred'][0],
            batch.volumes[VolumeType.GT_AFFINITIES].roi,
            batch.volumes[VolumeType.GT_AFFINITIES].resolution,
            interpolate=True)
        batch.loss = loss
        batch.iteration = self.solver.iter
        if VolumeType.LOSS_GRADIENT in request.volumes:
            diffs = self.net_io.get_output_diffs()
            batch.volumes[VolumeType.LOSS_GRADIENT] = Volume(
                diffs['aff_pred'][0],
                batch.volumes[VolumeType.GT_AFFINITIES].roi,
                batch.volumes[VolumeType.GT_AFFINITIES].resolution,
                interpolate=True)

        time_of_iteration = time.time() - start
        logger.info("Train process: iteration=%d loss=%f time=%f" %
                    (self.solver.iter, batch.loss, time_of_iteration))

        return batch
Exemple #8
0
class Train(BatchFilter):
    '''Performs one training iteration for each batch that passes through. 
    Adds the predicted affinities to the batch.
    '''
    def __init__(self, solver_parameters, use_gpu=None):

        # start training as a producer pool, so that we can gracefully exit if
        # anything goes wrong
        self.worker = ProducerPool([lambda gpu=use_gpu: self.__train(gpu)],
                                   queue_size=1)
        self.batch_in = multiprocessing.Queue(maxsize=1)

        self.solver_parameters = solver_parameters
        self.solver_initialized = False

    def setup(self):
        self.worker.start()

    def teardown(self):
        self.worker.stop()

    def prepare(self, request):

        # remove request parts that we provide
        for volume_type in [
                VolumeType.LOSS_GRADIENT, VolumeType.PRED_AFFINITIES
        ]:
            if volume_type in request.volumes:
                del request.volumes[volume_type]

    def process(self, batch, request):

        self.batch_in.put((batch, request))

        try:
            out = self.worker.get()
        except WorkersDied:
            raise TrainProcessDied()

        batch.volumes[VolumeType.PRED_AFFINITIES] = out.volumes[
            VolumeType.PRED_AFFINITIES]
        if VolumeType.LOSS_GRADIENT in request.volumes:
            batch.volumes[VolumeType.LOSS_GRADIENT] = out.volumes[
                VolumeType.LOSS_GRADIENT]
        batch.loss = out.loss
        batch.iteration = out.iteration

    def __train(self, use_gpu):

        start = time.time()

        if not self.solver_initialized:

            logger.info("Initializing solver...")

            if use_gpu is not None:

                logger.debug("Train process: using GPU %d" % use_gpu)
                caffe.enumerate_devices(False)
                caffe.set_devices((use_gpu, ))
                caffe.set_mode_gpu()
                caffe.select_device(use_gpu, False)

            self.solver = caffe.get_solver(self.solver_parameters)
            if self.solver_parameters.resume_from is not None:
                logger.debug("Train process: restoring solver state from " +
                             self.solver_parameters.resume_from)
                self.solver.restore(self.solver_parameters.resume_from)

            self.net_io = NetIoWrapper(self.solver.net)

            self.solver_initialized = True

        batch, request = self.batch_in.get()

        data = {
            'data':
            batch.volumes[VolumeType.RAW].data[np.newaxis, np.newaxis, :],
            'aff_label':
            batch.volumes[VolumeType.GT_AFFINITIES].data[np.newaxis, :],
        }

        if self.solver_parameters.train_state.get_stage(0) == 'euclid':
            logger.debug(
                "Train process: preparing input data for Euclidean training")
            self.__prepare_euclidean(batch, data)
        else:
            logger.debug(
                "Train process: preparing input data for Malis training")
            self.__prepare_malis(batch, data)

        self.net_io.set_inputs(data)

        loss = self.solver.step(1)
        # self.__consistency_check()
        output = self.net_io.get_outputs()
        batch.volumes[VolumeType.PRED_AFFINITIES] = Volume(
            output['aff_pred'][0],
            batch.volumes[VolumeType.GT_AFFINITIES].roi,
            batch.volumes[VolumeType.GT_AFFINITIES].resolution,
            interpolate=True)
        batch.loss = loss
        batch.iteration = self.solver.iter
        if VolumeType.LOSS_GRADIENT in request.volumes:
            diffs = self.net_io.get_output_diffs()
            batch.volumes[VolumeType.LOSS_GRADIENT] = Volume(
                diffs['aff_pred'][0],
                batch.volumes[VolumeType.GT_AFFINITIES].roi,
                batch.volumes[VolumeType.GT_AFFINITIES].resolution,
                interpolate=True)

        time_of_iteration = time.time() - start
        logger.info("Train process: iteration=%d loss=%f time=%f" %
                    (self.solver.iter, batch.loss, time_of_iteration))

        return batch

    def __prepare_euclidean(self, batch, data):

        gt_affinities = batch.volumes[VolumeType.GT_AFFINITIES]

        # initialize error scale with 1s
        error_scale = np.ones(gt_affinities.data.shape, dtype=np.float)

        # set error_scale to 0 in masked-out areas
        if VolumeType.GT_MASK in batch.volumes:
            self.__mask_error_scale(error_scale,
                                    batch.volumes[VolumeType.GT_MASK].data)
        if VolumeType.GT_IGNORE in batch.volumes:
            self.__mask_error_scale(error_scale,
                                    batch.volumes[VolumeType.GT_IGNORE].data)

        # in the masked-in area, compute the fraction of positive samples
        masked_in = error_scale.sum()
        num_pos = (gt_affinities.data * error_scale).sum()
        frac_pos = float(num_pos) / masked_in if masked_in > 0 else 0
        frac_pos = np.clip(frac_pos, 0.05, 0.95)
        frac_neg = 1.0 - frac_pos

        # compute the class weights for positive and negative samples
        w_pos = 1.0 / (2.0 * frac_pos)
        w_neg = 1.0 / (2.0 * frac_neg)

        # scale the masked-in error_scale with the class weights
        error_scale *= (data >= 0.5) * w_pos + (data < 0.5) * w_neg

        data['scale'] = error_scale[np.newaxis, :]

    def __mask_error_scale(self, error_scale, mask):
        for d in range(error_scale.shape[0]):
            error_scale[d] *= mask

    def __prepare_malis(self, batch, data):

        gt_labels = batch.volumes[VolumeType.GT_LABELS]
        next_id = gt_labels.data.max() + 1

        gt_pos_pass = gt_labels.data

        if VolumeType.GT_IGNORE in batch.volumes:

            gt_neg_pass = np.array(gt_labels.data)
            gt_neg_pass[batch.volumes[VolumeType.GT_IGNORE].data ==
                        0] = next_id

        else:

            gt_neg_pass = gt_pos_pass

        data['comp_label'] = np.array([[gt_neg_pass, gt_pos_pass]])
        data['nhood'] = batch.affinity_neighborhood[np.newaxis, np.newaxis, :]

        # Why don't we update gt_affinities in the same way?
        # -> not needed
        #
        # GT affinities are all 0 in the masked area (because masked area is
        # assumed to be set to background in batch.gt).
        #
        # In the negative pass:
        #
        #   We set all affinities inside GT regions to 1. Affinities in masked
        #   area as predicted. Belongs to one forground region (introduced
        #   above). But we only count loss on edges connecting different labels
        #   -> loss in masked-out area only from outside regions.
        #
        # In the positive pass:
        #
        #   We set all affinities outside GT regions to 0 -> no loss in masked
        #   out area.

    def __consistency_check(self):

        diffs = self.net_io.get_outputs()
        for k in diffs:
            assert not np.isnan(
                diffs[k]).any(), "Detected NaN in output diff " + k
Exemple #9
0
class Predict(BatchFilter):
    '''Augments the batch with the predicted affinities.
    '''
    def __init__(self, prototxt, weights, use_gpu=None):

        for f in [prototxt, weights]:
            if not os.path.isfile(f):
                raise RuntimeError("%s does not exist" % f)

        # start prediction as a producer pool, so that we can gracefully exit if
        # anything goes wrong
        self.worker = ProducerPool([lambda gpu=use_gpu: self.__predict(gpu)],
                                   queue_size=1)
        self.batch_in = multiprocessing.Queue(maxsize=1)

        self.prototxt = prototxt
        self.weights = weights
        self.net_initialized = False

    def setup(self):
        self.worker.start()

    def teardown(self):
        self.worker.stop()

    def prepare(self, request):

        # remove request parts that we provide
        if VolumeTypes.PRED_AFFINITIES in request.volumes:
            del request.volumes[VolumeTypes.PRED_AFFINITIES]

    def process(self, batch, request):

        self.batch_in.put(batch)

        try:
            out = self.worker.get()
        except WorkersDied:
            raise PredictProcessDied()

        affs = out.volumes[VolumeTypes.PRED_AFFINITIES]
        affs.roi = request.volumes[VolumeTypes.PRED_AFFINITIES]
        affs.resolution = batch.volumes[VolumeTypes.RAW].resolution

        batch.volumes[VolumeTypes.PRED_AFFINITIES] = affs

    def __predict(self, use_gpu):

        if not self.net_initialized:

            logger.info("Initializing solver...")

            if use_gpu is not None:

                logger.debug("Predict process: using GPU %d" % use_gpu)
                caffe.enumerate_devices(False)
                caffe.set_devices((use_gpu, ))
                caffe.set_mode_gpu()
                caffe.select_device(use_gpu, False)

            self.net = caffe.Net(self.prototxt, self.weights, caffe.TEST)
            self.net_io = NetIoWrapper(self.net)
            self.net_initialized = True

        start = time.time()

        batch = self.batch_in.get()

        fetch_time = time.time() - start

        self.net_io.set_inputs({
            'data':
            batch.volumes[VolumeTypes.RAW].data[np.newaxis, np.newaxis, :],
        })
        self.net.forward()
        output = self.net_io.get_outputs()

        predict_time = time.time() - start

        logger.info(
            "Predict process: time=%f (including %f waiting for batch)" %
            (predict_time, fetch_time))

        assert len(
            output['aff_pred'].shape
        ) == 5, "Got affinity prediction with unexpected number of dimensions, should be 1 (direction) + 3 (spatial) + 1 (batch, not used), but is %d" % len(
            output['aff_pred'].shape)
        batch.volumes[VolumeTypes.PRED_AFFINITIES] = Volume(
            output['aff_pred'][0], Roi(), (1, 1, 1))

        return batch
Exemple #10
0
class Train(GenericTrain):
    '''Caffe implementation of :class:`gunpowder.nodes.Train`.

    Args:

        solver_parameters (:class:``SolverParameters``): Parameters of the
            solver to use for training, contains the network description as
            well.

        inputs (dict): Dictionary from names of input layers in the network to
            :class:``ArrayKey`` or batch attribute name as string.

        outputs (dict): Dictionary from the names of output layers in the
            network to :class:``ArrayKey``. New arrays will be generated by
            this node for each entry (if requested downstream).

        gradients (dict): Dictionary from the names of output layers in the
            network to :class:``ArrayKey``. New arrays containing the
            gradient of an output with respect to the loss will be generated by
            this node for each entry (if requested downstream).

        array_specs (dict, optional): An optional dictionary of
            :class:`ArrayKey` to :class:`ArraySpec` to set the array specs
            generated arrays (``outputs`` and ``gradients``). This is useful
            to set the ``voxel_size``, for example, if they differ from the
            voxel size of the input arrays. Only fields that are not ``None``
            in the given :class:`ArraySpec` will be used.

        use_gpu (int): Which GPU to use. Set to ``None`` for CPU mode.
    '''
    def __init__(self,
                 solver_parameters,
                 inputs,
                 outputs,
                 gradients,
                 array_specs=None,
                 use_gpu=None):

        super(Train, self).__init__(inputs,
                                    outputs,
                                    gradients,
                                    array_specs,
                                    spawn_subprocess=True)
        self.solver_parameters = solver_parameters
        self.use_gpu = use_gpu
        self.solver = None
        self.net_io = None

    def start(self):

        logger.info("Initializing solver...")

        if self.use_gpu is not None:

            logger.debug("Train process: using GPU %d", self.use_gpu)
            caffe.enumerate_devices(False)
            caffe.set_devices((self.use_gpu, ))
            caffe.set_mode_gpu()
            caffe.select_device(self.use_gpu, False)

        self.solver = caffe.get_solver(self.solver_parameters)
        if self.solver_parameters.resume_from is not None:
            logger.debug("Train process: restoring solver state from %s",
                         self.solver_parameters.resume_from)
            self.solver.restore(self.solver_parameters.resume_from)

        names_net_outputs = self.outputs.keys() + self.gradients.keys()
        self.net_io = NetIoWrapper(self.solver.net, names_net_outputs)

    def train_step(self, batch, request):

        data = {}
        for input_name, network_input in self.inputs.items():
            if isinstance(network_input, ArrayKey):
                if network_input in batch.arrays:
                    data[input_name] = batch.arrays[network_input].data
                else:
                    logger.warn(
                        "batch does not contain %s, input %s will not "
                        "be set", network_input, input_name)
            elif isinstance(network_input, np.ndarray):
                data[input_name] = network_input
            elif isinstance(network_input, str):
                data[input_name] = getattr(batch, network_input)
            else:
                raise Exception(
                    "Unknown network input type {}, can't be given to "
                    "network".format(network_input))
        self.net_io.set_inputs(data)

        loss = self.solver.step(1)
        # self.__consistency_check()

        requested_outputs = {
            name: array_key
            for name, array_key in self.outputs.items()
            if array_key in request.array_specs
        }

        if requested_outputs:

            output = self.net_io.get_outputs()

            for output_name, array_key in requested_outputs.items():

                spec = self.spec[array_key].copy()
                spec.roi = request[array_key].roi
                batch.arrays[array_key] = Array(
                    output[output_name][0],  # strip #batch dimension
                    spec)

        requested_gradients = {
            name: array_key
            for name, array_key in self.gradients.items()
            if array_key in request.array_specs
        }

        if requested_gradients:

            diffs = self.net_io.get_output_diffs()

            for output_name, array_key in requested_gradients.items():

                spec = self.spec[array_key].copy()
                spec.roi = request[array_key].roi
                batch.arrays[array_key] = Array(
                    diffs[output_name][0],  # strip #batch dimension
                    spec)

        batch.loss = loss
        batch.iteration = self.solver.iter

    def __consistency_check(self):

        diffs = self.net_io.get_output_diffs()
        for k in diffs:
            assert not np.isnan(
                diffs[k]).any(), "Detected NaN in output diff " + k
class StupidPredict(object):
    '''Augments a batch with network predictions.

    Args:

        prototxt (string): Filename of the network prototxt.

        weights (string): Filename of the network weights.

        inputs (dict): Dictionary from the names of input layers in the
            network to :class:``VolumeType`` or batch attribute name as string.

        outputs (dict): Dictionary from the names of output layers in the
            network to :class:``VolumeType``. New volumes will be generated by
            this node for each entry (if requested downstream).

        volume_specs (dict, optional): An optional dictionary of
            :class:`VolumeType` to :class:`VolumeSpec` to set the volume specs
            generated volumes (``outputs``). This is useful to set the
            ``voxel_size``, for example, if they differ from the voxel size of
            the input volumes. Only fields that are not ``None`` in the given
            :class:`VolumeSpec` will be used.

        use_gpu (int): Which GPU to use. Set to ``None`` for CPU mode.
    '''
    def __init__(self,
                 prototxt,
                 weights,
                 inputs,
                 outputs,
                 volume_specs=None,
                 use_gpu=None):

        for f in [prototxt, weights]:
            if not os.path.isfile(f):
                raise RuntimeError("%s does not exist" % f)
        self.prototxt = prototxt
        self.weights = weights
        self.inputs = inputs
        self.outputs = outputs

        if use_gpu is not None:

            logger.debug("Predict process: using GPU %d" % use_gpu)
            caffe.enumerate_devices(False)
            caffe.set_devices((use_gpu, ))
            caffe.set_mode_gpu()
            caffe.select_device(use_gpu, False)

        self.net = caffe.Net(self.prototxt, self.weights, caffe.TEST)
        self.net_io = NetIoWrapper(self.net, self.outputs.values())

    def __call__(self, input_data):
        assert isinstance(input_data, dict)
        self.net_io.set_inputs(
            {input_name: data
             for input_name, data in input_data.items()})

        self.net.forward()
        output = self.net_io.get_outputs()
        return output
Exemple #12
0
class Predict(GenericPredict):
    '''Augments a batch with network predictions.

    Args:

        prototxt (string): Filename of the network prototxt.

        weights (string): Filename of the network weights.

        inputs (dict): Dictionary from the names of input layers in the
            network to :class:``ArrayKey`` or batch attribute name as string.

        outputs (dict): Dictionary from the names of output layers in the
            network to :class:``ArrayKey``. New arrays will be generated by
            this node for each entry (if requested downstream).

        array_specs (dict, optional): An optional dictionary of
            :class:`ArrayKey` to :class:`ArraySpec` to set the array specs
            generated arrays (``outputs``). This is useful to set the
            ``voxel_size``, for example, if they differ from the voxel size of
            the input arrays. Only fields that are not ``None`` in the given
            :class:`ArraySpec` will be used.

        use_gpu (int): Which GPU to use. Set to ``None`` for CPU mode.
    '''
    def __init__(self,
                 prototxt,
                 weights,
                 inputs,
                 outputs,
                 array_specs=None,
                 use_gpu=None):

        super(Predict, self).__init__(inputs,
                                      outputs,
                                      array_specs,
                                      spawn_subprocess=True)
        for f in [prototxt, weights]:
            if not os.path.isfile(f):
                raise RuntimeError("%s does not exist" % f)
        self.prototxt = prototxt
        self.weights = weights
        self.inputs = inputs
        self.outputs = outputs
        self.use_gpu = use_gpu

    def start(self):

        logger.info("Initializing solver...")

        if self.use_gpu is not None:

            logger.debug("Predict process: using GPU %d" % self.use_gpu)
            caffe.enumerate_devices(False)
            caffe.set_devices((self.use_gpu, ))
            caffe.set_mode_gpu()
            caffe.select_device(self.use_gpu, False)

        self.net = caffe.Net(self.prototxt, self.weights, caffe.TEST)
        self.net_io = NetIoWrapper(self.net, self.outputs.keys())

    def predict(self, batch, request):

        self.net_io.set_inputs({
            input_name: batch.arrays[array_key].data
            for input_name, array_key in self.inputs.items()
        })

        self.net.forward()
        output = self.net_io.get_outputs()

        for output_name, array_key in self.outputs.items():
            spec = self.spec[array_key].copy()
            spec.roi = request[array_key].roi
            batch.arrays[array_key] = Array(
                output[output_name][0],  # strip #batch dimension
                spec)

        return batch