Files
cdtools/CDTools/tools/analysis.py
T

143 lines
5.5 KiB
Python

from __future__ import division, print_function
import torch as t
import numpy as np
from CDTools.tools import cmath
from CDTools.tools import image_processing as ip
__all__ = ['orthogonalize_probes','standardize']
from matplotlib import pyplot as plt
def orthogonalize_probes(probes):
"""Orthogonalizes a set of incoherently mixing probes
The strategy is to define a reduced orthogonal basis that spans
all of the retrieved probes, and then build the density matrix
defined by the probes in that basis. After diagonalization, the
eigenvectors can be recast into the original basis and returned
Args:
probes (t.Tensor) : n x (image) size tensor, a stack of probes
Returns:
(t.Tensor) : n x (image) size tensor, a stack of probes
"""
try:
probes = cmath.torch_to_complex(probes.detach().cpu())
except:
pass
bases = []
coefficients = np.zeros((probes.shape[0],probes.shape[0]), dtype=np.complex64)
for i, probe in enumerate(probes):
ortho_probe = np.copy(probe)
for j, basis in enumerate(bases):
coefficients[j,i] = np.sum(basis.conj()*ortho_probe)
ortho_probe -= basis * coefficients[j,i]
coefficients[i,i] = np.sqrt(np.sum(np.abs(ortho_probe)**2))
bases.append(ortho_probe / coefficients[i,i])
density_mat = coefficients.dot(np.conj(coefficients).transpose())
eigvals, eigvecs = np.linalg.eigh(density_mat)
ortho_probes = []
for i in range(len(eigvals)):
coefficients = np.sqrt(eigvals[i]) * eigvecs[:,i]
print(coefficients)
probe = np.zeros(bases[0].shape, dtype=np.complex64)
for coefficient, basis in zip(coefficients, bases):
probe += basis * coefficient
ortho_probes.append(probe)
return cmath.complex_to_torch(np.stack(ortho_probes[::-1]))
def standardize(probe, obj, obj_slice=None, correct_ramp=False):
"""Standardizes a probe and object to prepare them for comparison
There are a number of ambiguities in the definition of a ptychographic
reconstruction. This function makes an explicit choice for each ambiguity
to allow comparisons between independent reconstructions without confusing
these ambiguities for real differences between the reconstructions.
The ambiguities and standardizations are:
* Probe and object can be scaled inversely to one another
* So we set the probe intensity to an average per-pixel value of 1
* The probe and object can aquire equal and opposite phase ramps
* So we set the centroid of the FFT of the probe to zero frequency
* The probe and object can each acquire an arbitrary overall phase
* So we set the phase of the sum of all values of both the probe and object to 0
When dealing with the properties of the object, a slice is used by
default as the edges of the object often are dominated by unphysical
noise. The default slice is from 3/8 to 5/8 of the way across.
Args:
probe (t.tensor) : tensor or numpy array storing a retrieved probe
obj (t.tensor) : tensor or numpy array storing a retrieved probe
obj_slice (slice) : optional, a slice to take from the object for calculating normalizations
correct_ramp (bool) : Default False, whether to correct for the relative phase ramps
"""
# First, we normalize the probe intensity to a fixed value.
probe_np = False
if isinstance(probe, np.ndarray):
probe = cmath.complex_to_torch(probe).to(t.float32)
probe_np = True
obj_np = False
if isinstance(obj, np.ndarray):
obj = cmath.complex_to_torch(obj).to(t.float32)
obj_np = True
normalization = t.sqrt(t.sum(cmath.cabssq(probe)) / (len(probe.view(-1))/2))
probe = probe / normalization
obj = obj * normalization
# Default slice of the object to use for alignment, etc.
if obj_slice is None:
obj_slice = np.s_[(obj.shape[0]//8)*3:(obj.shape[0]//8)*5,
(obj.shape[1]//8)*3:(obj.shape[1]//8)*5]
if correct_ramp:
# Need to check if this is actually working and, if noy, why not
center_freq = ip.centroid_sq(cmath.fftshift(t.fft(probe,2)),comp=True)
center_freq -= (t.tensor(probe.shape[:-1]) // 2).to(t.float32)
center_freq /= t.tensor(probe.shape[:-1]).to(t.float32)
Is, Js = np.mgrid[:probe.shape[0],:probe.shape[1]]
probe_phase_ramp = cmath.expi(2*np.pi *
(center_freq[0] * t.tensor(Is).to(t.float32) +
center_freq[1] * t.tensor(Js).to(t.float32)))
probe = cmath.cmult(probe, cmath.cconj(probe_phase_ramp))
Is, Js = np.mgrid[:obj.shape[0],:obj.shape[1]]
obj_phase_ramp = cmath.expi(2*np.pi *
(center_freq[0] * t.tensor(Is).to(t.float32) +
center_freq[1] * t.tensor(Js).to(t.float32)))
obj = cmath.cmult(obj, obj_phase_ramp)
# Then, we set them to consistent absolute phases
probe_angle = cmath.cphase(t.sum(probe,dim=(0,1)))
obj_angle = cmath.cphase(t.sum(obj[obj_slice],dim=(0,1)))
probe = cmath.cmult(probe, cmath.expi(-probe_angle))
obj = cmath.cmult(obj, cmath.expi(-obj_angle))
if probe_np:
probe = cmath.torch_to_complex(probe.detach().cpu())
if obj_np:
obj = cmath.torch_to_complex(obj.detach().cpu())
return probe, obj