//  [python]
def histogram(
        self, img=None, nbins=255, range=None, log_scale=False, copy=True, out=None
    ):
        """Compute the histogram of a set of data.

        :param img: input image. If None, use the one already on the device
        :param nbins: number of bins
        :param range: the lower and upper range of the bins.  If not provided,
                    range is simply ``(a.min(), a.max())``.  Values outside the
                    range are ignored. The first element of the range must be
                    less than or equal to the second.
        :param log_scale: perform the binning in lograrithmic scale.
                         Open to extension
        :param copy: unset to directly use the input buffer without copy
        :param out: use a provided array for offering the result
        :return: histogram (size=nbins), edges (size=nbins+1)
        API similar to numpy
        """
        assert img.shape == self.buffer_shape

        input_array = self.to_float(img, copy=copy, out=self.cl_mem["image0_d"])
        events = []
        with self.sem:
            input_array, output_array = self._get_in_out_buffers(
                input_array, copy=False, out=out, out_dtype=numpy.int32, out_size=nbins
            )

            if range is None:
                # measure actually the bounds
                size = numpy.int32(n.prod(self.shape))
                if self.wg_red == 1:
                    #  Probably on MacOS CPU WG==1 --> serial code.
                    kernel = self.kernels.get_kernel("max_min_serial")

                    evt = kernel(
                        self.queue,
                        (1,),
                        (1,),
                        input_array.data,
                        size,
                        self.cl_mem["max_min_d"].data,
                    )
                    events.append(EventDescription("max_min_serial", evt))
                else:
                    stage1 = self.kernels.max_min_reduction_stage1
                    stage2 = self.kernels.max_min_reduction_stage2
                    local_mem = pyopencl.LocalMemory(
                        int(self.wg_red * 2 * numpy.dtype("float32").itemsize)
                    )
                    k1 = stage1(
                        self.queue,
                        (int(self.wg_red**2),),
                        (int(self.wg_red),),
                        input_array.data,
                        self.cl_mem["tmp_max_min_d"].data,
                        size,
                        local_mem,
                    )
                    k2 = stage2(
                        self.queue,
                        (int(self.wg_red),),
                        (int(self.wg_red),),
                        self.cl_mem["tmp_max_min_d"].data,
                        self.cl_mem["max_min_d"].data,
                        local_mem,
                    )

                    events += [
                        EventDescription("max_min_stage1", k1),
                        EventDescription("max_min_stage2", k2),
                    ]
                maxi, mini = self.cl_mem["max_min_d"].get()
            else:
                mini = numpy.float32(min(range))
                maxi = numpy.float32(max(range))
            device = self.ctx.devices[0]
            nb_engines = device.max_compute_units
            tmp_size = nb_engines * nbins
            name = "tmp_int32_%s_d" % (tmp_size)
            if name not in self.cl_mem:
                tmp_array = self.cl_mem[name] = pyopencl.array.empty(
                    self.queue, (tmp_size,), numpy.int32
                )
            else:
                tmp_array = self.cl_mem[name]

            edge_name = "tmp_float32_%s_d" % (nbins + 1)
            if edge_name not in self.cl_mem:
                edges_array = self.cl_mem[edge_name] = pyopencl.array.empty(
                    self.queue, (nbins + 1,), numpy.float32
                )
            else:
                edges_array = self.cl_mem[edge_name]

            shared = pyopencl.LocalMemory(numpy.dtype(numpy.int32).itemsize * nbins)

            # Handle log-scale
            if log_scale:
                map_operation = numpy.int32(1)
            else:
                map_operation = numpy.int32(0)
            kernel = self.kernels.get_kernel("histogram")
            wg = min(
                device.max_work_group_size,
                1 << (int(ceil(log(nbins, 2)))),
                self.kernels.max_workgroup_size(kernel),
            )
            evt = kernel(
                self.queue,
                (wg * nb_engines,),
                (wg,),
                input_array.data,
                numpy.int32(input_array.size),
                mini,
                maxi,
                map_operation,
                output_array.data,
                edges_array.data,
                numpy.int32(nbins),
                tmp_array.data,
                self.cl_mem["cnt_d"].data,
                shared,
            )
            events.append(EventDescription("histogram", evt))

        if self.profile:
            self.events += events

        if out is None:
            res = output_array.get()
            return res, edges_array.get()
        else:
            output_array.finish()
            return output_array, edges_array