# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import numpy as np
from scipy.ndimage import map_coordinates
from scipy.ndimage.interpolation import shift
from scipy.optimize import curve_fit, minimize
[docs]def get_image_quadrants(IM, reorient=True, symmetry_axis=None,
use_quadrants=(True, True, True, True)):
"""
Given an image (m,n) return its 4 quadrants Q0, Q1, Q2, Q3
as defined below.
Parameters
----------
IM : 2D np.array
Image data shape (rows, cols)
reorient : boolean
Reorient quadrants to match the orientation of Q0 (top-right)
symmetry_axis : int or tuple
can have values of ``None``, ``0``, ``1``, or ``(0,1)`` and specifies
no symmetry, vertical symmetry axis, horizontal symmetry axis, and both vertical
and horizontal symmetry axes. Quadrants are averaged.
See Note.
use_quadrants : boolean tuple
Include quadrant (Q0, Q1, Q2, Q3) in the symmetry combination(s)
and final image
Returns
-------
Q0, Q1, Q2, Q3 : tuple of 2D np.arrays
shape: (``rows//2+rows%2, cols//2+cols%2``)
all oriented in the same direction as Q0 if ``reorient=True``
Notes
-----
The symmetry_axis keyword averages quadrants like this: ::
+--------+--------+
| Q1 * | * Q0 |
| * | * |
| * | * | cQ1 | cQ0
+--------o--------+ --(output) -> ----o----
| * | * | cQ2 | cQ3
| * | * |
| Q2 * | * Q3 | cQi == averaged combined quadrants
+--------+--------+
symmetry_axis = None - individual quadrants
symmetry_axis = 0 (vertical) - average Q0+Q1, and Q2+Q3
symmetry_axis = 1 (horizontal) - average Q1+Q2, and Q0+Q3
symmetry_axis = (0, 1) (both) - combine and average all 4 quadrants
The end results look like this: ::
(0) symmetry_axis = None
returned image Q1 | Q0
----o----
Q2 | Q3
(1) symmetry_axis = 0
Combine: Q01 = Q0 + Q1, Q23 = Q2 + Q3
returned image Q01 | Q01
-----o-----
Q23 | Q23
(2) symmetry_axis = 1
Combine: Q12 = Q1 + Q2, Q03 = Q0 + Q3
returned image Q12 | Q03
-----o-----
Q12 | Q03
(3) symmetry_axis = (0, 1)
Combine all quadrants: Q = Q0 + Q1 + Q2 + Q3
returned image Q | Q
---o--- all quadrants equivalent
Q | Q
"""
IM = np.atleast_2d(IM)
if not isinstance(symmetry_axis, (list, tuple)):
# if the user supplies an int, make it into a 1-element list:
symmetry_axis = [symmetry_axis]
n, m = IM.shape
n_c = n // 2 + n % 2
m_c = m // 2 + m % 2
# define 4 quadrants of the image
# see definition above
Q0 = IM[:n_c, -m_c:]
Q1 = IM[:n_c, :m_c]
Q2 = IM[-n_c:, :m_c]
Q3 = IM[-n_c:, -m_c:]
if reorient:
Q1 = np.fliplr(Q1)
Q3 = np.flipud(Q3)
Q2 = np.fliplr(np.flipud(Q2))
if isinstance(symmetry_axis, tuple) and not reorient:
raise ValueError(
'In order to add quadrants (i.e., to apply horizontal or \
vertical symmetry), you must reorient the image.')
if 0 in symmetry_axis: # vertical axis image symmetry
Q0 = Q1 = (Q0*use_quadrants[0]+Q1*use_quadrants[1])/\
(use_quadrants[0] + use_quadrants[1])
Q2 = Q3 = (Q2*use_quadrants[2]+Q3*use_quadrants[3])/\
(use_quadrants[2] + use_quadrants[3])
if 1 in symmetry_axis: # horizontal axis image symmetry
Q1 = Q2 = (Q1*use_quadrants[1]+Q2*use_quadrants[2])/\
(use_quadrants[1] + use_quadrants[2])
Q0 = Q3 = (Q0*use_quadrants[0]+Q3*use_quadrants[3])/\
(use_quadrants[0] + use_quadrants[3])
return Q0, Q1, Q2, Q3
[docs]def put_image_quadrants(Q, odd_size=True, symmetry_axis=None):
"""
Reassemble image from 4 quadrants Q = (Q0, Q1, Q2, Q3)
The reverse process to get_image_quadrants(reorient=True)
Note: the quadrants should all be oriented as Q0, the upper right quadrant
Parameters
----------
Q: tuple of np.array (Q0, Q1, Q2, Q3)
Image quadrants all oriented as Q0
shape (``rows//2+rows%2, cols//2+cols%2``) ::
+--------+--------+
| Q1 * | * Q0 |
| * | * |
| * | * |
+--------o--------+
| * | * |
| * | * |
| Q2 * | * Q3 |
+--------+--------+
odd_size: boolean
Whether final image is odd or even pixel size
odd size will trim 1 row from Q1, Q0, and 1 column from Q1, Q2
symmetry_axis : int or tuple
impose image symmetry
``symmetry_axis = 0 (vertical) - Q0 == Q1 and Q3 == Q2``
``symmetry_axis = 1 (horizontal) - Q2 == Q1 and Q3 == Q0``
Returns
-------
IM : np.array
Reassembled image of shape (rows, cols): ::
symmetry_axis =
None 0 1 (0,1)
Q1 | Q0 Q1 | Q1 Q1 | Q0 Q1 | Q1
----o---- or ----o---- or ----o---- or ----o----
Q2 | Q3 Q2 | Q2 Q1 | Q0 Q1 | Q1
"""
Q0, Q1, Q2, Q3 = Q
if not isinstance(symmetry_axis, (list, tuple)):
# if the user supplies an int, make it into a 1-element list:
symmetry_axis = [symmetry_axis]
if 0 in symmetry_axis:
Q0 = Q1
Q3 = Q2
if 1 in symmetry_axis:
Q2 = Q1
Q3 = Q0
if not odd_size:
Top = np.concatenate((np.fliplr(Q0), Q1), axis=1)
Bottom = np.flipud(np.concatenate((np.fliplr(Q2), Q3), axis=1))
else:
# odd size image remove extra row/column added in get_image_quadrant()
Top = np.concatenate(
(np.fliplr(Q[1])[:-1, :-1], Q[0][:-1, :]), axis=1)
Bottom = np.flipud(
np.concatenate((np.fliplr(Q[2])[:, :-1], Q[3]), axis=1))
IM = np.concatenate((Top, Bottom), axis=0)
return IM
# def center_image(data, center, n, ndim=2):
# """
# This centers the image at the given center and makes it of size n by n
#
# THIS FUNCTION IS DEPRECIATED.
# All centering functions should be moves to abel.tools.center
# """
#
# Nh, Nw = data.shape
# n_2 = n//2
# if ndim == 1:
# cx = int(center)
# im = np.zeros((1, 2*n))
# im[0, n-cx:n-cx+Nw] = data
# im = im[:, n_2:n+n_2]
# # This is really not efficient
# # Processing 2D image with identical rows while we just want a
# # 1D slice
# im = np.repeat(im, n, axis=0)
#
# elif ndim == 2:
# cx, cy = np.asarray(center, dtype='int')
#
# # Make an array of zeros that is large enough for cropping or padding:
# sz = 2*np.round(n + np.max((Nw, Nh)))
# im = np.zeros((sz, sz))
#
# # Set center of "zeros image" to be the data
# im[sz//2-cy:sz//2-cy+Nh, sz//2-cx:sz//2-cx+Nw] = data
#
# # Crop padded image to size n
# # note the n%2 which return the appropriate image size for both
# # odd and even images
# im = im[sz//2-n_2:n_2+sz//2+n % 2, sz//2-n_2:n_2+sz//2+n % 2]
#
# else:
# raise ValueError
#
# return im
#
#
# def center_image_asym(data, center_column, n_vert, n_horz, verbose=False):
# """
# This centers a (rectangular) image at the given center_column
# and makes it of size n_vert by n_horz
#
# THIS FUNCTION IS DEPRECIATED.
# All centering functions should be moved to abel.tools.center
# """
#
# if data.ndim > 2:
# raise ValueError("Array to be centered must be 1- or 2-dimensional")
#
# c_im = np.copy(data) # make a copy of the original data for manipulation
# data_vert, data_horz = c_im.shape
# pad_mode = str("constant")
#
# if data_horz % 2 == 0:
# # Add column of zeros to the extreme right
# # to give data array odd columns
# c_im = np.pad(c_im, ((0, 0), (0, 1)), pad_mode, constant_values=0)
# data_vert, data_horz = c_im.shape # update data dimensions
#
# delta_h = int(center_column - data_horz//2)
# if delta_h != 0:
# if delta_h < 0:
# # Specified center is to the left of nominal center
# # Add compensating zeroes on the left edge
# c_im = np.pad(c_im, ((0, 0), (2*np.abs(delta_h), 0)), pad_mode,
# constant_values=0)
# data_vert, data_horz = c_im.shape
# else:
# # Specified center is to the right of nominal center
# # Add compensating zeros on the right edge
# c_im = np.pad(c_im, ((0, 0), (0, 2*delta_h)), pad_mode,
# constant_values=0)
# data_vert, data_horz = c_im.shape
#
# if n_vert >= data_vert and n_horz >= data_horz:
# pad_up = (n_vert - data_vert)//2
# pad_down = n_vert - data_vert - pad_up
# pad_left = (n_horz - data_horz)//2
# pad_right = n_horz - data_horz - pad_left
#
# c_im = np.pad(
# c_im, ((pad_up, pad_down), (pad_left, pad_right)),
# pad_mode, constant_values=0)
#
# elif n_vert >= data_vert and n_horz < data_horz:
# pad_up = (n_vert - data_vert)//2
# pad_down = n_vert - data_vert - pad_up
# crop_left = (data_horz - n_horz)//2
# crop_right = data_horz - n_horz - crop_left
# if verbose:
# print("Warning: cropping %d pixels from the sides \
# of the image" % crop_left)
# c_im = np.pad(
# c_im[:, crop_left:-crop_right], ((pad_up, pad_down), (0, 0)),
# pad_mode, constant_values=0)
#
# elif n_vert < data_vert and n_horz >= data_horz:
# crop_up = (data_vert - n_vert)//2
# crop_down = data_vert - n_vert - crop_up
# pad_left = (n_horz - data_horz)//2
# pad_right = n_horz - data_horz - pad_left
# if verbose:
# print("Warning: cropping %d pixels from top and bottom \
# of the image" % crop_up)
# c_im = np.pad(
# c_im[crop_up:-crop_down], ((0, 0), (pad_left, pad_right)),
# pad_mode, constant_values=0)
#
# elif n_vert < data_vert and n_horz < data_horz:
# crop_up = (data_vert - n_vert)//2
# crop_down = data_vert - n_vert - crop_up
# crop_left = (data_horz - n_horz)//2
# crop_right = data_horz - n_horz - crop_left
# if verbose:
# print("Warning: cropping %d pixels from top and bottom \
# and %d pixels from the sides of the image " % (
# crop_up, crop_left))
# c_im = c_im[crop_up:-crop_down, crop_left:-crop_right]
#
# else:
# raise ValueError('Input data dimensions incompatible \
# with chosen basis set.')
#
# return c_im