From ab2a05d62f1c5433551cb4126b67b11e1e493d43 Mon Sep 17 00:00:00 2001
From: Rafael Gort <rafael.gort@xfel.eu>
Date: Thu, 17 Sep 2020 13:33:28 +0200
Subject: [PATCH] processing 4 quadrants in parallel

---
 src/toolbox_scs/detectors/dssc.py             |  24 +-
 .../detectors/image_manipulation.py           | 336 ------------------
 2 files changed, 15 insertions(+), 345 deletions(-)
 delete mode 100644 src/toolbox_scs/detectors/image_manipulation.py

diff --git a/src/toolbox_scs/detectors/dssc.py b/src/toolbox_scs/detectors/dssc.py
index 213940c..9ee7b47 100644
--- a/src/toolbox_scs/detectors/dssc.py
+++ b/src/toolbox_scs/detectors/dssc.py
@@ -389,9 +389,11 @@ class DSSC_Generator:
 
     def full_detector(self, dssc_data):
         print('assemble full detector: ')
-        # determine dimension of full detector
-        q2 = self.quadrant(2, dssc_data)
-        [qdimy, qdimx] = q2.shape
+        # assemble full detector
+        q_data = joblib.Parallel(n_jobs=4)(joblib.delayed(self.quadrant)(i, dssc_data) for i in [1,2,3,4])
+
+        # determine dimensions
+        [qdimy, qdimx] = q_data[0].shape
         fdimy = np.max(
                     [np.abs(self.quad_geodat[1, 0]) + self.quad_geodat[1, 1],
                      np.abs(self.quad_geodat[3, 0]) + self.quad_geodat[3, 1]]
@@ -401,26 +403,30 @@ class DSSC_Generator:
                      np.abs(self.quad_geodat[0, 0]) + self.quad_geodat[0, 1]]
                     ) + 2 * qdimx
 
-        # assemble full detector
         full_dssc = np.zeros((fdimy, fdimx))
+
+        # quadrant 2
         full_dssc[self.quad_geodat[1, 0]:qdimy+self.quad_geodat[1, 0], 0:qdimx ] \
-            = self.quadrant(2, dssc_data)
+            = q_data[1]
+
+        # quadrant 1
         full_dssc[qdimy+self.quad_geodat[1, 0]+self.quad_geodat[1, 1]:2*qdimy+self.quad_geodat[1, 0]+self.quad_geodat[1, 1],
                   self.quad_geodat[0, 0]:self.quad_geodat[0, 0]+qdimx] \
-            = self.quadrant(1, dssc_data)#
+            = q_data[0]
 
+        # quadrant 4
         dummy1 = [qdimy + self.quad_geodat[1, 0] + self.quad_geodat[1, 1] + self.quad_geodat[3, 0],
                 2 * qdimy + self.quad_geodat[1, 0] + self.quad_geodat[1, 1] + self.quad_geodat[3, 0]]
         dummy2 = [self.quad_geodat[0, 0] + self.quad_geodat[0, 1] + qdimx,
                 self.quad_geodat[0, 0] + self.quad_geodat[0, 1] + 2*qdimx]
-        full_dssc[dummy1[0]:dummy1[1], dummy2[0]:dummy2[1]] = self.quadrant(4, dssc_data)
+        full_dssc[dummy1[0]:dummy1[1], dummy2[0]:dummy2[1]] = q_data[3]
 
+        # quadrant 3
         dummy3 = [self.quad_geodat[1, 0] + self.quad_geodat[1, 1] + self.quad_geodat[3, 0] - self.quad_geodat[3, 1],
                 qdimy+self.quad_geodat[1, 0] + self.quad_geodat[1, 1] + self.quad_geodat[3, 0] - self.quad_geodat[3, 1]]
         dummy4 = [self.quad_geodat[0, 0] + self.quad_geodat[0, 1] + qdimx + self.quad_geodat[2, 0],
                 self.quad_geodat[0, 0] + self.quad_geodat[0, 1] + 2*qdimx + self.quad_geodat[2, 0]]
-
-        full_dssc[dummy3[0]:dummy3[1],dummy4[0]:dummy4[1]] = self.quadrant(3, dssc_data)
+        full_dssc[dummy3[0]:dummy3[1],dummy4[0]:dummy4[1]] = q_data[2]
 
         return full_dssc
 
diff --git a/src/toolbox_scs/detectors/image_manipulation.py b/src/toolbox_scs/detectors/image_manipulation.py
deleted file mode 100644
index e4ecc9a..0000000
--- a/src/toolbox_scs/detectors/image_manipulation.py
+++ /dev/null
@@ -1,336 +0,0 @@
-"""
-Collection of Filters for image processing:
-Thiran filters for delay shifters. The filter is used for image shearing.
-"""
-import logging
-
-import numpy as np
-import math as math
-from skimage.util import pad as skpad
-from skimage import data as skdata
-
-log = logging.getLogger(__name__)
-
-
-def n_choose_k(n, k):
-    """
-    :n choose k problem: returns the number of ways to choose a subset k from a
-    set of n elements this is eqivalent to:
-        factorial(n)/factorial(k)/factorial(n-k)
-
-    written by A. Scherz
-    """
-    return math.factorial(n) / math.factorial(k) / math.factorial(n - k)
-
-
-def thiran_type_I(order=3):
-    return Thiran(1, order)
-
-
-def thiran_type_II(order=2):
-    return Thiran(2, order)
-
-
-class Thiran:
-    """
-    there are two types of 1D fractional filters based on Thiran filters. both
-    types can have an order N=1 to inf. some types, provide simple algorithms
-    for applying the delay to a signal. for type II and orders larger than N.
-    he transfer and impulse response fct have to be calculated and are more
-    computational expensive
-    The generator defaults the Thiran filter of type I and order N=3
-    For more details see of the filters and algorithms,
-        see pg. 685, IEEE Trans. on Im. Proc. 17, 679 (2008)
-
-    written by A. Scherz, XFEL, 2012-11-26
-    updated by A. Scherz, XFEL, 2019-02-11 for python
-    added to SpeckleToolbox by A. Scherz, XFEL, 2020-08-06
-    """
-
-    type_I = staticmethod(thiran_type_I)
-
-    type_II = staticmethod(thiran_type_II)
-
-    def __init__(self, filter_type=1, order=3, delay=0):
-        self._type = filter_type
-        self._n = order
-        self._delay = delay
-        self._b = np.zeros(order)
-        self._a_minus_delay = 0
-        self._a_plus_delay = 0
-
-    def __str__(self):
-        print('Thiran 1D Fractional filter')
-        print('  Type : ' + self._type.__str__())
-        print(' Order : ' + self._n.__str__())
-        print(' Delay : ' + self._delay.__str__())
-
-        return ''
-
-    def apply_delay(self, signal):
-        # periodic boundaries are important. add padding of at least the order
-        # N left and right
-        # this should be applied before calling this method for type I
-        shifted_signal = np.array(signal)
-
-        if self._type == 1:     # type I Thiran
-            for j in range(len(shifted_signal) - self._n - 1, self._n - 1, -1):
-                for k in range(1, self._n + 1):
-                    shifted_signal[j] += self._b[k - 1] \
-                        * (shifted_signal[j - k] - shifted_signal[j + k])
-        else:   # type II of order N = 2
-            for j in range(self._n - 1, len(shifted_signal) - self._n - 1, 1):
-                shifted_signal[j] += self._a_plus_delay \
-                    * (shifted_signal[j - 1] - shifted_signal[j + 1])
-            for j in range(len(shifted_signal) - self._n - 1, self._n - 1, -1):
-                shifted_signal[j] += self._a_minus_delay \
-                    * (shifted_signal[j + 1] - shifted_signal[j - 1])
-
-        return shifted_signal  # shifted signal
-
-    def delay(self, tau):
-        if (tau < -0.5) or (tau > 0.5):
-            raise ValueError('WARNING out of range: delay: 0.0 - 0.5')
-        self._delay = tau
-        # update coefficients for delay
-        self._coefficients()
-        return self
-
-    def _coefficients(self):
-        for k in range(1, self._n + 1):
-            p_k = 1
-            for l in range(self._n + 1):
-                p_k *= (self._delay - l) / (self._delay - l - k)
-            # n_choose_k function is factorial(n)/factorial(k)/factorial(n-k)
-            self._b[k - 1] = (-1) ** k * p_k * n_choose_k(self._n, k)
-        self._a_plus_delay = \
-            (-4 + self._delay**2 + np.sqrt(12 - 3*self._delay**2))
-        self._a_minus_delay = self._a_plus_delay
-        self._a_plus_delay /= (3*self._delay + 2 + self._delay**2)
-        self._a_minus_delay /= (-3*self._delay + 2 + self._delay**2)
-
-
-def is_even(num):
-    """
-    is num an even number?
-    :param num: integer
-    :return: boolean True = num is even number
-    """
-    return not (num & 1)
-
-
-def next_power_of_2(x=1):
-    """
-    Returns the next higher power of 2 compared to x
-    :param x: current dimension
-    :return: next higher power of 2
-    """
-    return 1 << (x - 1).bit_length()
-
-
-def get_im_center(im: np.array) -> (int, int):
-    """
-    the center of image is the DC components with respect to discrete Fourier
-    transformation
-    :returns center pos (int):
-    """
-    return int(im.shape[0] / 2), int(im.shape[1] / 2)
-
-
-def pad4fft(im: np.array = None,
-            dim: [None, bool, int] = None,
-            fill: [None, str, float] = None) -> np.array:
-    """
-    pad image using dim parameter and pad image with given fill
-    :param im:
-    :param dim: None == up to next power of 2, True/False == oversampling, or
-    int with given dimension
-    :param fill: None == zeros, str == special format see skimage.pad, float ==
-    fill value
-    :return: object itself
-    """
-    if im is not None:
-        im = np.array(im).squeeze()
-    else:
-        im = skdata.lena()
-    #print('image size is ' + str(im.shape))
-    #print('dimension is supposed to be ' + str(dim))
-
-    if dim is None:
-        dim = max((next_power_of_2(im.shape[0]), next_power_of_2(im.shape[1])))
-        dim = (dim, dim)
-    elif type(dim) is bool:
-        oversampling = dim
-        print('is boolean option')
-        dim = max((next_power_of_2(im.shape[0]), next_power_of_2(im.shape[1])))
-        dim *= 2 if oversampling else 1  # another factor 2 for oversampling
-        dim = (dim, dim)
-    elif (dim[0] < im.shape[0]) and (dim[1] < im.shape[1]):
-        return im
-
-    #print('dimension is ' + str(dim))
-
-    w0 = ((dim[0] - im.shape[0]) // 2)
-    w1 = ((dim[1] - im.shape[1]) // 2)
-
-    width0 = (w0, w0) if is_even(im.shape[0]) else (w0, w0 + 1)
-    width1 = (w1, w1) if is_even(im.shape[1]) else (w1, w1 + 1)
-
-    if fill is None:
-        im = skpad(im, (width0, width1), mode='constant', constant_values=0)
-    elif type(fill) is str:
-        im = skpad(im, (width0, width1), mode=fill)
-    else:
-        im = skpad(im, (width0, width1), mode='constant', constant_values=fill)
-
-    return im
-
-
-def shear_im(im: np.array, delay: float,
-             im_axis: int = 1,
-             thiran: Thiran = thiran_type_I()) -> np.array:
-    """
-    : direction (str): 1=='hor' or 0=='ver', required
-    : thiran: Thiran type 1 of order 3 is default (default)
-    """
-    im = np.float32(im)
-    cx, cy = get_im_center(im)
-    # print(im.shape)
-    # print(cx, cy)
-    if im_axis == 0:
-        for k in range(2 * cy):
-            shift = delay * (k - cy)
-            # full pixel shifts
-            im[:, k] = np.roll(im[:, k], round(shift))
-            # now shift a pixel fraction
-            im[:, k] = thiran.delay(shift-round(shift)).apply_delay(im[:, k])
-            #  thiran_filter(im[:, k], thiran(order, shift - round(shift)))
-        # print('apply vertical shear')
-    elif im_axis == 1:
-        for k in range(2 * cx):
-            shift = delay * (k - cx)
-            # full pixel shifts
-            im[k, :] = np.roll(im[k, :], round(shift))
-            # now shift a pixel fraction: shift between +/- [0-0.5]
-            im[k, :] = thiran.delay(shift - round(shift)).apply_delay(im[k, :])
-            # thiran_filter(im[k, :], thiran(order, shift - round(shift)))
-        #print('apply horizontal shear')
-    else:
-        # TODO: throw exception
-        print('direction undefined')
-
-    return im
-
-
-def convert_cart2hex(im, thiran=thiran_type_II()):
-    """
-    [ imH ] = ConversionCart2Hex( imC, orderN, showImage )
-    imC: image in cartesian coord.
-    thiran: Thiran filter type 1 of order 3 is default
-    show: optional, if true an image representation in hex is shown.
-    note: show hex image may take considerable amount of time for larger
-    images!)
-    Nov.28, 2012, A. Scherz
-        """
-    # first step: zero pad the image for extending boundaries
-
-    im_hex = pad4fft(im, dim=[2*im.shape[0], 2*im.shape[1]], fill='reflect')
-    cx, cy = get_im_center(im_hex)
-
-    sqrt3 = math.sqrt(3)
-    shear1 = sqrt3 - math.sqrt(6 / sqrt3)
-    shear2 = math.sqrt(sqrt3 / 6)
-    shear3 = 2 - math.sqrt(6 / sqrt3)
-    # this is conversion from cartesian to hexagonal lattice
-    im_hex = shear_im(im_hex, -shear1, im_axis=0, thiran=thiran)
-    im_hex = shear_im(im_hex, -shear2, im_axis=1, thiran=thiran)
-    im_hex = shear_im(im_hex, -shear3, im_axis=0, thiran=thiran)
-
-    # reorder of pixels required
-    height = int(np.round(cart2hex([im.shape[0], 0]))[0])
-    width = int(np.round(cart2hex([0, im.shape[1]]))[1])
-    y0 = height % 2
-
-    # print(height, width, y0)
-    for y in range(-height // 2, height // 2 + y0 + 1):
-        im_hex[y - y0 + cx, :] = np.roll(im_hex[y - y0 + cx, :],
-                                         math.floor((y - y0) / 2))
-
-    # now crop image
-    im_hex = im_hex[-height // 2 + cx: height // 2 + cx + y0,
-                    -width // 2 + cy: width // 2 + cy + 1]
-
-    # TODO check height and width
-    # implement show
-
-    return im_hex
-
-
-def convert_hex2cart(im_hex, thiran=thiran_type_II()):
-    """
-    [ imH ] = ConversionCart2Hex( imC, orderN, showImage )
-    imC: image in cartesian coord.
-    thiran: Thiran filter type 1 of order 3 is default
-    show: optional, if true an image representation in hex is shown.
-    note: show hex image may take considerable amount of time for larger
-    images!)
-    Nov.28, 2012, A. Scherz
-    """
-    # first step: zero pad the image for extending boundaries
-
-    im = pad4fft(im_hex,
-                 dim=[2*im_hex.shape[0], 2*im_hex.shape[1]],
-                 fill='reflect')
-    cx, cy = get_im_center(im)
-
-    # need to skew image to hexagonal shape before apply shear operations
-    height = im_hex.shape[0]
-    y0 = height % 2
-    for y in range(- height // 2, height // 2 + y0 + 1):
-        im[y - y0 + cx, :] = np.roll(im[y - y0 + cx, :],
-                                     - math.floor((y + y0) / 2))
-
-    sqrt3 = math.sqrt(3)
-    shear1 = sqrt3 - math.sqrt(6 / sqrt3)
-    shear2 = math.sqrt(sqrt3 / 6)
-    shear3 = 2 - math.sqrt(6 / sqrt3)
-    # this is conversion from cartesian to hexagonal lattice
-    im = shear_im(im, shear3, im_axis=0, thiran=thiran)
-    im = shear_im(im, shear2, im_axis=1, thiran=thiran)
-    im = shear_im(im, shear1, im_axis=0, thiran=thiran)
-
-    # the image should now form a rectangular box of following size:
-    height = int(np.round(hex2cart([im_hex.shape[0], 0]))[0])
-    width = int(np.round(hex2cart([0, im_hex.shape[1]]))[1])
-    y0 = height % 2
-
-#    print(height, width, y0)
-#    for y in range(-height // 2, height // 2 + y0 + 1):
-#        im_hex[y - y0 + cx, :] = np.roll(im_hex[y - y0 + cx, :],
-#                                         math.floor((y - y0) / 2))
-
-    # now crop image
-    im = im[-height // 2 + cx: height // 2 + cx + y0,
-            -width // 2 + cy: width // 2 + cy + 1]
-
-    # TODO check height and width
-    # implement show
-
-    return im
-
-
-def cart2hex(r_cart):
-    # TODO: this function can eventually move somewhere else
-    sqrt3 = math.sqrt(3)
-    # conversion matrix
-    rhex = math.sqrt(2 / sqrt3) * np.array([[1, 1 / 2], [0, sqrt3 / 2]])
-
-    return np.dot(rhex, r_cart)
-
-
-def hex2cart(r_hex):
-    sqrt3 = math.sqrt(3)
-    rhex = math.sqrt(2 / sqrt3) * np.array([[1, 1 / 2], [0, sqrt3 / 2]])
-    rcart = np.linalg.inv(rhex)
-
-    return np.dot(rcart, r_hex)
-- 
GitLab