summaryrefslogtreecommitdiff
path: root/silx/opencl/image.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/opencl/image.py')
-rw-r--r--silx/opencl/image.py387
1 files changed, 0 insertions, 387 deletions
diff --git a/silx/opencl/image.py b/silx/opencl/image.py
deleted file mode 100644
index 65e2d5e..0000000
--- a/silx/opencl/image.py
+++ /dev/null
@@ -1,387 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Project: silx
-# https://github.com/silx-kit/silx
-#
-# Copyright (C) 2012-2017 European Synchrotron Radiation Facility, Grenoble, France
-#
-# Principal author: Jérôme Kieffer (Jerome.Kieffer@ESRF.eu)
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-# .
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-# .
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-
-"""A general purpose library for manipulating 2D images in 1 or 3 colors
-
-"""
-from __future__ import absolute_import, print_function, with_statement, division
-
-
-__author__ = "Jerome Kieffer"
-__license__ = "MIT"
-__date__ = "12/02/2018"
-__copyright__ = "2012-2017, ESRF, Grenoble"
-__contact__ = "jerome.kieffer@esrf.fr"
-
-import os
-import logging
-import numpy
-from collections import OrderedDict
-from math import floor, ceil, sqrt, log
-
-from .common import pyopencl, kernel_workgroup_size
-from .processing import EventDescription, OpenclProcessing, BufferDescription
-
-if pyopencl:
- mf = pyopencl.mem_flags
-logger = logging.getLogger(__name__)
-
-
-class ImageProcessing(OpenclProcessing):
-
- kernel_files = ["cast", "map", "max_min", "histogram"]
-
- converter = {numpy.dtype(numpy.uint8): "u8_to_float",
- numpy.dtype(numpy.int8): "s8_to_float",
- numpy.dtype(numpy.uint16): "u16_to_float",
- numpy.dtype(numpy.int16): "s16_to_float",
- numpy.dtype(numpy.uint32): "u32_to_float",
- numpy.dtype(numpy.int32): "s32_to_float",
- }
-
- def __init__(self, shape=None, ncolors=1, template=None,
- ctx=None, devicetype="all", platformid=None, deviceid=None,
- block_size=None, memory=None, profile=False):
- """Constructor of the ImageProcessing class
-
- :param ctx: actual working context, left to None for automatic
- initialization from device type or platformid/deviceid
- :param devicetype: type of device, can be "CPU", "GPU", "ACC" or "ALL"
- :param platformid: integer with the platform_identifier, as given by clinfo
- :param deviceid: Integer with the device identifier, as given by clinfo
- :param block_size: preferred workgroup size, may vary depending on the
- out come of the compilation
- :param memory: minimum memory available on device
- :param profile: switch on profiling to be able to profile at the kernel
- level, store profiling elements (makes code slightly slower)
- """
- OpenclProcessing.__init__(self, ctx=ctx, devicetype=devicetype,
- platformid=platformid, deviceid=deviceid,
- block_size=block_size, memory=memory, profile=profile)
- if template is not None:
- shape = template.shape
- if len(shape) > 2:
- self.ncolors = shape[2]
- self.shape = shape[:2]
- else:
- self.ncolors = 1
- self.shape = shape
- else:
- self.ncolors = ncolors
- self.shape = shape
- assert shape is not None
- self.buffer_shape = self.shape if self.ncolors == 1 else self.shape + (self.ncolors,)
- kernel_files = [os.path.join("image", i) for i in self.kernel_files]
- self.compile_kernels(kernel_files,
- compile_options="-DNB_COLOR=%i" % self.ncolors)
- if self.ncolors == 1:
- img_shape = self.shape
- else:
- img_shape = self.shape + (self.ncolors,)
-
- buffers = [BufferDescription("image0_d", img_shape, numpy.float32, None),
- BufferDescription("image1_d", img_shape, numpy.float32, None),
- BufferDescription("image2_d", img_shape, numpy.float32, None),
- BufferDescription("max_min_d", 2, numpy.float32, None),
- BufferDescription("cnt_d", 1, numpy.int32, None), ]
- # Temporary buffer for max-min reduction
- self.wg_red = kernel_workgroup_size(self.program, self.kernels.max_min_reduction_stage1)
- if self.wg_red > 1:
- self.wg_red = min(self.wg_red,
- numpy.int32(1 << int(floor(log(sqrt(numpy.prod(self.shape)), 2)))))
- tmp = BufferDescription("tmp_max_min_d", 2 * self.wg_red, numpy.float32, None)
- buffers.append(tmp)
- self.allocate_buffers(buffers, use_array=True)
- self.cl_mem["cnt_d"].fill(0)
-
- def __repr__(self):
- return "ImageProcessing for shape=%s, %i colors initalized on %s" % \
- (self.shape, self.ncolors, self.ctx.devices[0].name)
-
- def _get_in_out_buffers(self, img=None, copy=True, out=None,
- out_dtype=None, out_size=None):
- """Internal method used to select the proper buffers before processing.
-
- :param img: expects a numpy array or a pyopencl.array of dim 2 or 3
- :param copy: set to False to directly re-use a pyopencl array
- :param out: provide an output buffer to store the result
- :param out_dtype: enforce the type of the output buffer (optional)
- :param out_size: enforce the size of the output buffer (optional)
- :return: input_buffer, output_buffer
-
- Nota: this is not locked.
- """
- events = []
- if out is not None and isinstance(out, pyopencl.array.Array):
- if (out_size or out_dtype) is not None:
- if out_size is not None:
- assert out.size > out_size
- if out_dtype is not None:
- assert out_dtype == out.dtype
- else: # assume it is same size and type as weoking buffer
- assert out.shape == self.buffer_shape
- assert out.dtype == numpy.float32
- out.finish()
- output_array = out
- else:
- if out_dtype != numpy.float32 and out_size:
- name = "%s_%s_d" % (numpy.dtype(out_dtype), out_size)
- if name not in self.cl_mem:
- output_array = self.cl_mem[name] = pyopencl.array.empty(self.queue, (out_size,), out_dtype)
- else:
- output_array = self.cl_mem[name]
- else:
- output_array = self.cl_mem["image2_d"]
-
- if img is None:
- input_array = self.cl_mem["image1_d"]
- if isinstance(img, pyopencl.array.Array):
- if copy:
- evt = pyopencl.enqueue_copy(self.queue, self.cl_mem["image1_d"].data, img.data)
- input_array = self.cl_mem["image1_d"]
- events.append(EventDescription("copy D->D", evt))
- else:
- img.finish()
- input_array = img
- evt = None
- else:
- # assume this is numpy
- if img.dtype.itemsize > 4:
- logger.warning("Casting to float32 on CPU")
- evt = pyopencl.enqueue_copy(self.queue, self.cl_mem["image1_d"].data, numpy.ascontiguousarray(img, numpy.float32))
- input_array = self.cl_mem["image1_d"]
- events.append(EventDescription("cast+copy H->D", evt))
- else:
- evt = pyopencl.enqueue_copy(self.queue, self.cl_mem["image1_d"].data, numpy.ascontiguousarray(img))
- input_array = self.cl_mem["image1_d"]
- events.append(EventDescription("copy H->D", evt))
- if self.profile:
- self.events += events
- return input_array, output_array
-
- def to_float(self, img, copy=True, out=None):
- """ Takes any array and convert it to a float array for ease of processing.
-
- :param img: expects a numpy array or a pyopencl.array of dim 2 or 3
- :param copy: set to False to directly re-use a pyopencl array
- :param out: provide an output buffer to store the result
- """
- assert img.shape == self.buffer_shape
-
- events = []
- with self.sem:
- input_array, output_array = self._get_in_out_buffers(img, copy, out)
- if (img.dtype.itemsize > 4) or (img.dtype == numpy.float32):
- # copy device -> device, already there as float32
- ev = pyopencl.enqueue_copy(self.queue, output_array.data, input_array.data)
- events.append(EventDescription("copy D->D", ev))
- else:
- # Cast to float:
- name = self.converter[img.dtype]
- kernel = self.kernels.get_kernel(name)
- ev = kernel(self.queue, (self.shape[1], self.shape[0]), None,
- input_array.data, output_array.data,
- numpy.int32(self.shape[1]), numpy.int32(self.shape[0])
- )
- events.append(EventDescription("cast %s" % name, ev))
-
- if self.profile:
- self.events += events
- if out is None:
- res = output_array.get()
- return res
- else:
- output_array.finish()
- return output_array
-
- def normalize(self, img, mini=0.0, maxi=1.0, copy=True, out=None):
- """Scale the intensity of the image so that the minimum is 0 and the
- maximum is 1.0 (or any value suggested).
-
- :param img: numpy array or pyopencl array of dim 2 or 3 and of type float
- :param mini: Expected minimum value
- :param maxi: expected maxiumum value
- :param copy: set to False to use directly the input buffer
- :param out: provides an output buffer. prevents a copy D->H
-
- This uses a min/max reduction in two stages plus a map operation
- """
- assert img.shape == self.buffer_shape
- events = []
- with self.sem:
- input_array, output_array = self._get_in_out_buffers(img, copy, out)
- size = numpy.int32(numpy.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)
- ed = EventDescription("max_min_serial", evt)
- events.append(ed)
- else:
- stage1 = self.kernels.max_min_reduction_stage1
- stage2 = self.kernels.max_min_reduction_stage2
- local_mem = pyopencl.LocalMemory(int(self.wg_red * 8))
- 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)]
-
- evt = self.kernels.normalize_image(self.queue, (self.shape[1], self.shape[0]), None,
- input_array.data, output_array.data,
- numpy.int32(self.shape[1]), numpy.int32(self.shape[0]),
- self.cl_mem["max_min_d"].data,
- numpy.float32(mini), numpy.float32(maxi))
- events.append(EventDescription("normalize", evt))
- if self.profile:
- self.events += events
-
- if out is None:
- res = output_array.get()
- return res
- else:
- output_array.finish()
- return output_array
-
- 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(numpy.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