summaryrefslogtreecommitdiff
path: root/silx/opencl/backprojection.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/opencl/backprojection.py')
-rw-r--r--silx/opencl/backprojection.py397
1 files changed, 0 insertions, 397 deletions
diff --git a/silx/opencl/backprojection.py b/silx/opencl/backprojection.py
deleted file mode 100644
index 65a9836..0000000
--- a/silx/opencl/backprojection.py
+++ /dev/null
@@ -1,397 +0,0 @@
-#!/usr/bin/env python
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 European Synchrotron Radiation Facility
-#
-# 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.
-#
-# ###########################################################################*/
-"""Module for (filtered) backprojection on the GPU"""
-
-from __future__ import absolute_import, print_function, with_statement, division
-
-__authors__ = ["A. Mirone, P. Paleo"]
-__license__ = "MIT"
-__date__ = "25/01/2019"
-
-import logging
-import numpy as np
-
-from .common import pyopencl
-from .processing import EventDescription, OpenclProcessing, BufferDescription
-from .sinofilter import SinoFilter
-from .sinofilter import fourier_filter as fourier_filter_
-from ..utils.deprecation import deprecated
-
-if pyopencl:
- mf = pyopencl.mem_flags
- import pyopencl.array as parray
-else:
- raise ImportError("Please install pyopencl in order to use opencl backprojection")
-logger = logging.getLogger(__name__)
-
-
-def _sizeof(Type):
- """
- return the size (in bytes) of a scalar type, like the C behavior
- """
- return np.dtype(Type).itemsize
-
-
-def _idivup(a, b):
- """
- return the integer division, plus one if `a` is not a multiple of `b`
- """
- return (a + (b - 1)) // b
-
-
-class Backprojection(OpenclProcessing):
- """A class for performing the backprojection using OpenCL"""
- kernel_files = ["backproj.cl", "array_utils.cl"]
-
- def __init__(self, sino_shape, slice_shape=None, axis_position=None,
- angles=None, filter_name=None, ctx=None, devicetype="all",
- platformid=None, deviceid=None, profile=False,
- extra_options=None):
- """Constructor of the OpenCL (filtered) backprojection
-
- :param sino_shape: shape of the sinogram. The sinogram is in the format
- (n_b, n_a) where n_b is the number of detector bins
- and n_a is the number of angles.
- :param slice_shape: Optional, shape of the reconstructed slice. By
- default, it is a square slice where the dimension
- is the "x dimension" of the sinogram (number of
- bins).
- :param axis_position: Optional, axis position. Default is
- `(shape[1]-1)/2.0`.
- :param angles: Optional, a list of custom angles in radian.
- :param filter_name: Optional, name of the filter for FBP. Default is
- the Ram-Lak filter.
- :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 profile: switch on profiling to be able to profile at the kernel
- level, store profiling elements (makes code slightly
- slower)
- :param extra_options: Advanced extra options in the form of a dict.
- Current options are: cutoff, use_numpy_fft
- """
- # OS X enforces a workgroup size of 1 when the kernel has
- # synchronization barriers if sys.platform.startswith('darwin'):
- # assuming no discrete GPU
- # raise NotImplementedError("Backprojection is not implemented on CPU for OS X yet")
-
- OpenclProcessing.__init__(self, ctx=ctx, devicetype=devicetype,
- platformid=platformid, deviceid=deviceid,
- profile=profile)
-
- self._init_geometry(sino_shape, slice_shape, angles, axis_position,
- extra_options)
- self._allocate_memory()
- self._compute_angles()
- self._init_kernels()
- self._init_filter(filter_name)
-
- def _init_geometry(self, sino_shape, slice_shape, angles, axis_position,
- extra_options):
- """Geometry Initialization
-
- :param sino_shape: shape of the sinogram. The sinogram is in the format
- (n_b, n_a) where n_b is the number of detector bins
- and n_a is the number of angles.
- :param slice_shape: shape of the reconstructed slice. By
- default, it is a square slice where the dimension
- is the "x dimension" of the sinogram (number of
- bins).
- :param angles: list of projection angles in radian.
- :param axis_position: axis position
- :param dict extra_options: Advanced extra options
- """
- self.shape = sino_shape
- self.num_bins = np.int32(sino_shape[1])
- self.num_projs = np.int32(sino_shape[0])
- self.angles = angles
- if slice_shape is None:
- self.slice_shape = (self.num_bins, self.num_bins)
- else:
- self.slice_shape = slice_shape
- self.dimrec_shape = (
- _idivup(self.slice_shape[0], 32) * 32,
- _idivup(self.slice_shape[1], 32) * 32
- )
- if axis_position:
- self.axis_pos = np.float32(axis_position)
- else:
- self.axis_pos = np.float32((sino_shape[1] - 1.) / 2)
- self.axis_array = None # TODO: add axis correction front-end
- self._init_extra_options(extra_options)
-
- def _init_extra_options(self, extra_options):
- """Backprojection extra option initialization
-
- :param dict extra_options: Advanced extra options
- """
- self.extra_options = {
- "cutoff": 1.,
- "use_numpy_fft": False,
- # It is axis_pos - (num_bins-1)/2 in PyHST
- "gpu_offset_x": 0., #self.axis_pos - (self.num_bins - 1) / 2.,
- "gpu_offset_y": 0., #self.axis_pos - (self.num_bins - 1) / 2.
- }
- if extra_options is not None:
- self.extra_options.update(extra_options)
-
- def _allocate_memory(self):
- # Host memory
- self.slice = np.zeros(self.dimrec_shape, dtype=np.float32)
- self._use_textures = self.check_textures_availability()
-
- # Device memory
- self.buffers = [
- BufferDescription("_d_slice", self.dimrec_shape, np.float32, mf.READ_WRITE),
- BufferDescription("d_sino", self.shape, np.float32, mf.READ_WRITE), # before transferring to texture (if available)
- BufferDescription("d_cos", (self.num_projs,), np.float32, mf.READ_ONLY),
- BufferDescription("d_sin", (self.num_projs,), np.float32, mf.READ_ONLY),
- BufferDescription("d_axes", (self.num_projs,), np.float32, mf.READ_ONLY),
- ]
- self.allocate_buffers(use_array=True)
- self.d_sino = self.cl_mem["d_sino"] # shorthand
-
- # Texture memory (if relevant)
- if self._use_textures:
- self._allocate_textures()
-
- # Local memory
- self.local_mem = 256 * 3 * _sizeof(np.float32) # constant for all image sizes
-
- def _compute_angles(self):
- if self.angles is None:
- self.angles = np.linspace(0, np.pi, self.num_projs, False)
- h_cos = np.cos(self.angles).astype(np.float32)
- h_sin = np.sin(self.angles).astype(np.float32)
- self.cl_mem["d_cos"][:] = h_cos[:]
- self.cl_mem["d_sin"][:] = h_sin[:]
- if self.axis_array:
- self.cl_mem["d_axes"][:] = self.axis_array.astype(np.float32)[:]
- else:
- self.cl_mem["d_axes"][:] = np.ones(self.num_projs, dtype="f") * self.axis_pos
-
- def _init_kernels(self):
- compile_options = None
- if not(self._use_textures):
- compile_options = "-DDONT_USE_TEXTURES"
- OpenclProcessing.compile_kernels(
- self,
- self.kernel_files,
- compile_options=compile_options
- )
- # check that workgroup can actually be (16, 16)
- self.compiletime_workgroup_size = self.kernels.max_workgroup_size("backproj_cpu_kernel")
- # Workgroup and ndrange sizes are always the same
- self.wg = (16, 16)
- self.ndrange = (
- _idivup(int(self.dimrec_shape[1]), 32) * self.wg[0],
- _idivup(int(self.dimrec_shape[0]), 32) * self.wg[1]
- )
- # Prepare arguments for the kernel call
- if not(self._use_textures):
- d_sino_ref = self.d_sino.data
- else:
- d_sino_ref = self.d_sino_tex
- self._backproj_kernel_args = (
- # num of projections (int32)
- self.num_projs,
- # num of bins (int32)
- self.num_bins,
- # axis position (float32)
- self.axis_pos,
- # d_slice (__global float32*)
- self.cl_mem["_d_slice"].data,
- # d_sino (__read_only image2d_t or float*)
- d_sino_ref,
- # gpu_offset_x (float32) 
- np.float32(self.extra_options["gpu_offset_x"]),
- # gpu_offset_y (float32)
- np.float32(self.extra_options["gpu_offset_y"]),
- # d_cos (__global float32*)
- self.cl_mem["d_cos"].data,
- # d_sin (__global float32*)
- self.cl_mem["d_sin"].data,
- # d_axis (__global float32*)
- self.cl_mem["d_axes"].data,
- # shared mem (__local float32*)
- self._get_local_mem()
- )
-
- def _allocate_textures(self):
- """
- Allocate the texture for the sinogram.
- """
- self.d_sino_tex = self.allocate_texture(self.shape)
-
- def _init_filter(self, filter_name):
- """Filter initialization
-
- :param str filter_name: filter name
- """
- self.filter_name = filter_name or "ram-lak"
- self.sino_filter = SinoFilter(
- self.shape,
- ctx=self.ctx,
- filter_name=self.filter_name,
- extra_options=self.extra_options,
- )
-
- def _get_local_mem(self):
- return pyopencl.LocalMemory(self.local_mem) # constant for all image sizes
-
- def _cpy2d_to_slice(self, dst):
- ndrange = (int(self.slice_shape[1]), int(self.slice_shape[0]))
- slice_shape_ocl = np.int32(ndrange)
- wg = None
- kernel_args = (
- dst.data,
- self.cl_mem["_d_slice"].data,
- np.int32(self.slice_shape[1]),
- np.int32(self.dimrec_shape[1]),
- np.int32((0, 0)),
- np.int32((0, 0)),
- slice_shape_ocl
- )
- return self.kernels.cpy2d(self.queue, ndrange, wg, *kernel_args)
-
- def _transfer_to_texture(self, sino):
- if isinstance(sino, parray.Array):
- return self._transfer_device_to_texture(sino)
- sino2 = sino
- if not(sino.flags["C_CONTIGUOUS"] and sino.dtype == np.float32):
- sino2 = np.ascontiguousarray(sino, dtype=np.float32)
- if not(self._use_textures):
- ev = pyopencl.enqueue_copy(
- self.queue,
- self.d_sino.data,
- sino2
- )
- what = "transfer filtered sino H->D buffer"
- ev.wait()
- else:
- ev = pyopencl.enqueue_copy(
- self.queue,
- self.d_sino_tex,
- sino2,
- origin=(0, 0),
- region=self.shape[::-1]
- )
- what = "transfer filtered sino H->D texture"
- return EventDescription(what, ev)
-
- def _transfer_device_to_texture(self, d_sino):
- if not(self._use_textures):
- if id(self.d_sino) == id(d_sino):
- return
- ev = pyopencl.enqueue_copy(
- self.queue,
- self.d_sino.data,
- d_sino
- )
- what = "transfer filtered sino D->D buffer"
- ev.wait()
- else:
- ev = pyopencl.enqueue_copy(
- self.queue,
- self.d_sino_tex,
- d_sino.data,
- offset=0,
- origin=(0, 0),
- region=self.shape[::-1]
- )
- what = "transfer filtered sino D->D texture"
- return EventDescription(what, ev)
-
- def backprojection(self, sino, output=None):
- """Perform the backprojection on an input sinogram
-
- :param sino: sinogram.
- :param output: optional, output slice.
- If provided, the result will be written in this array.
- :return: backprojection of sinogram
- """
- events = []
- with self.sem:
- events.append(self._transfer_to_texture(sino))
- # Call the backprojection kernel
- if not(self._use_textures):
- kernel_to_call = self.kernels.backproj_cpu_kernel
- else:
- kernel_to_call = self.kernels.backproj_kernel
- kernel_to_call(
- self.queue,
- self.ndrange,
- self.wg,
- *self._backproj_kernel_args
- )
- # Return
- if output is None:
- res = self.cl_mem["_d_slice"].get()
- res = res[:self.slice_shape[0], :self.slice_shape[1]]
- else:
- res = output
- self._cpy2d_to_slice(output)
-
- # /with self.sem
- if self.profile:
- self.events += events
-
- return res
-
- def filtered_backprojection(self, sino, output=None):
- """
- Compute the filtered backprojection (FBP) on a sinogram.
-
- :param sino: sinogram (`np.ndarray` or `pyopencl.array.Array`)
- with the shape (n_projections, n_bins)
- :param output: output (`np.ndarray` or `pyopencl.array.Array`).
- If nothing is provided, a new numpy array is returned.
- """
- # Filter
- self.sino_filter(sino, output=self.d_sino)
- # Backproject
- res = self.backprojection(self.d_sino, output=output)
- return res
-
- __call__ = filtered_backprojection
-
-
- # -------------------
- # - Compatibility -
- # -------------------
-
- @deprecated(replacement="Backprojection.sino_filter", since_version="0.10")
- def filter_projections(self, sino, rescale=True):
- self.sino_filter(sino, output=self.d_sino)
-
-
-
-def fourier_filter(sino, filter_=None, fft_size=None):
- return fourier_filter_(sino, filter_=filter_, fft_size=fft_size)
-