Source code for nabu.estimation.cor_sino
"""
This module provides global definitions and methods to compute COR in extrem
Half Acquisition mode
"""
__authors__ = ["C. Nemoz", "H.Payno"]
__license__ = "MIT"
__date__ = "13/04/2021"
import numpy as np
from scipy.signal import convolve2d
from ..resources.logger import LoggerOrPrint
[docs]
def schift(mat, val):
ker = np.zeros((3, 3))
s = 1.0
if val < 0:
s = -1.0
val = s * val
ker[1, 1] = 1 - val
if s > 0:
ker[1, 2] = val
else:
ker[1, 0] = val
mat = convolve2d(mat, ker, mode="same")
return mat
[docs]
class SinoCor:
"""
This class has 2 methods:
- overlap. Find a rough estimate of COR
- accurate. Try to refine COR to 1/10 pixel
"""
def __init__(self, img_1, img_2, logger=None):
""" """
self.logger = LoggerOrPrint(logger)
self.sx = img_1.shape[1]
# algorithm cannot accept odd number of projs. This is handled in the SinoCORFinder class.
nproj2 = img_1.shape[0]
# extract upper and lower part of sinogram, flipping H the upper part
self.data1 = img_1
self.data2 = img_2
self.rcor_abs = round(self.sx / 2.0)
self.cor_acc = round(self.sx / 2.0)
# parameters for overlap sino - rough estimation
# default sliding ROI is 20% of the width of the detector
# the maximum size of ROI in the "right" case is 2*(self.sx - COR)
# ex: 2048 pixels, COR= 2000, window_width should not exceed 96!
self.window_width = round(self.sx / 5)
[docs]
def overlap(self, side="right", window_width=None):
"""
Compute COR by minimizing difference of circulating ROI
- side: preliminary knowledge if the COR is on right or left
- window_width: width of ROI that will slide on the other part of the sinogram
by default, 20% of the width of the detector.
"""
if window_width is None:
window_width = self.window_width
if not (window_width & 1):
window_width -= 1
# number of pixels where the window will "slide".
n = self.sx - int(window_width)
nr = range(n)
dmax = 1000000000.0
imax = 0
# Should we do both right and left and take the minimum "diff" of the 2 ?
# windows self.data2 moves over self.data1, measure the width of the histogram and retains the smaller one.
if side == "right":
for i in nr:
imout = self.data1[:, n - i : n - i + window_width] - self.data2[:, 0:window_width]
diff = imout.max() - imout.min()
if diff < dmax:
dmax = diff
imax = i
self.cor_abs = self.sx - (imax + window_width + 1.0) / 2.0
self.cor_rel = self.sx / 2 - (imax + window_width + 1.0) / 2.0
else:
for i in nr:
imout = self.data1[:, i : i + window_width] - self.data2[:, self.sx - window_width : self.sx]
diff = imout.max() - imout.min()
if diff < dmax:
dmax = diff
imax = i
self.cor_abs = (imax + window_width - 1.0) / 2
self.cor_rel = self.cor_abs - self.sx / 2.0 - 1
if imax < 1:
self.logger.warning("sliding width %d seems too large!" % window_width)
self.rcor_abs = round(self.cor_abs)
return self.rcor_abs
[docs]
def accurate(self, neighborhood=7, shift_value=0.1):
"""
refine the calculation around COR integer pre-calculated value
The search will be executed in the defined neighborhood
Parameters
-----------
neighborhood: int
Parameter for accurate calculation in the vicinity of the rough estimate.
It must be an odd number.
0.1 pixels float shifts will be performed over this number of pixel
"""
# define the H-size (odd) of the window one can use to find the best overlap moving finely over ng pixels
if not (neighborhood & 1):
neighborhood += 1
ng2 = int(neighborhood / 2)
# pleft and pright are the number of pixels available on the left and the right of the cor position
# to slide a window
pleft = self.rcor_abs - ng2
pright = self.sx - self.rcor_abs - ng2 - 1
# the maximum window to slide is restricted by the smaller side
if pleft > pright:
p_sign = 1
xwin = 2 * (self.sx - self.rcor_abs - ng2) - 1
else:
p_sign = -1
xwin = 2 * (self.rcor_abs - ng2) + 1
# Note that xwin is odd
xc1 = self.rcor_abs - int(xwin / 2)
xc2 = self.sx - self.rcor_abs - int(xwin / 2) - 1
im1 = self.data1[:, xc1 : xc1 + xwin]
im2 = self.data2[:, xc2 : xc2 + xwin]
pixs = p_sign * (np.arange(neighborhood) - ng2)
diff0 = 1000000000.0
isfr = shift_value * np.arange(10)
self.cor_acc = self.rcor_abs
for pix in pixs:
x0 = xc1 + pix
for isf in isfr:
if isf != 0:
ims = schift(self.data1[:, x0 : x0 + xwin].copy(), -p_sign * isf)
else:
ims = self.data1[:, x0 : x0 + xwin]
imout = ims - self.data2[:, xc2 : xc2 + xwin]
diff = imout.max() - imout.min()
if diff < diff0:
self.cor_acc = self.rcor_abs + (pix + p_sign * isf) / 2.0
diff0 = diff
return self.cor_acc
# Aliases
estimate_cor_coarse = overlap
estimate_cor_fine = accurate
[docs]
class SinoCorInterface:
"""
A class that mimics the interface of CenterOfRotation, while calling SinoCor
"""
def __init__(self, logger=None, **kwargs):
self._logger = logger
[docs]
def find_shift(self, img_1, img_2, side="right", window_width=None, neighborhood=7, shift_value=0.1, **kwargs):
cor_finder = SinoCor(img_1, img_2, logger=self._logger)
cor_finder.estimate_cor_coarse(side=side, window_width=window_width)
cor = cor_finder.estimate_cor_fine(neighborhood=neighborhood, shift_value=shift_value)
# offset will be added later - keep compatibility with result from AlignmentBase.find_shift()
return cor - img_1.shape[1] / 2