pyprProxOperators module
This file provides proximity operators and projectors for the Python Phase Retrieval (pyPR) toolbox.
""" This file provides proximity operators and projectors for the Python Phase Retrieval (pyPR) toolbox. """ from pyprUtilities import * import sys import numpy as np import pyShearLab2D as pySL def approximateMagnitudeProjection(input_data, Psi): """ Calculates an approximate projection onto a regularized set of measurements. This method can easily be extend to use input_data.noise.type and then calculate different error distances accordingly. Input: input_data: Input_data structure. Psi: Current iterate / wave function. """ if calculateKLDistance(input_data, Psi) >= input_data["noise"]["error"]: outputWave = magnitudeProjection(input_data, Psi) else: outputWave = Psi return outputWave def constraintProximityOperator(input_data, Psi, gamma): """ Applies Projection/Proximity Operator depending on the given constraint. Input: input_data Input_data structure. Psi Object to be projected. gamma Parameter (only for proximity operators) """ proximityOperatorName = retrieveProximityName(input_data) if (proximityOperatorName == "proxSupportConstraint") or (proximityOperatorName == "proxSupportRangeConstraint") or (proximityOperatorName == "proxRangeConstraint"): outputPsi = eval(proximityOperatorName+"(input_data, Psi)") elif proximityOperatorName == "proxShearlet": #outputPsi = eval(proximityOperatorName, input_data, Psi, gamma) outputPsi = eval(proximityOperatorName+"(input_data, Psi, gamma)") else: sys.exit("The provided proximityOperatorName " + proximityOperatorName + " is not supported." + "Check constraintProximityOperator in pyprProxOperators.py for details.") return outputPsi def magnitudeProjection(input_data, Psi): """ Calculates the projection onto a magnitude constraint set. Input: input_data Input_data structure. Psi Object / wave function to be projected. """ propagatedWave = wavePropagator(input_data, Psi, 1) projectedWave = MagProj(input_data["problem"]["data"], propagatedWave) outputWave = wavePropagator(input_data, projectedWave, -1) return outputWave def proxRangeConstraint(input_data, Psi): """ Implements range constraint for different types of objects, e.h. positivity, negative phase, and others. Input: input_data Input_data structure. Psi Object / wave function to be projected. """ if input_data["problemType"] == "phase": PsiPhase = np.angle(Psi) if "range" not in input_data["problem"]["constraint"]: # no info, hence negativitiy assumed. PsiPhase [PsiPhase >= 0] = 0 else: if input_data["problem"]["constraint"]["range"] == "positive": PsiPhase [PsiPhase <= 0] = 0 return np.exp(1j * PsiPhase) elif input_data["problemType"] == "amplitude": PsiAmplitude = np.abs(Psi) PsiAmplitude[ PsiAmplitude >= 1 ] = 1 return PsiAmplitude elif input_data["problemType"]["mixed"]: PsiPhase = np.angle(Psi) PsiAmplitude = np.abs(Psi) if "range" not in input_data["problem"]["constraint"]: PsiPhase[ PsiPhase <= 0 ] = 0 PsiAmplitude[ PsiAmplitude >= 1 ] = 1 else: if input_data["problem"]["constraint"]["range"] == "positive": PsiPhase[ PsiPhase >= 0 ] = 0 PsiAmplitude[ PsiAmplitude >= 1] = 1 return PsiAmplitude * np.exp(1j * PsiPhase) elif input_data["problemType"] == "real": if "range" not in input_data["problem"]["constraint"]: Psi[np.real(Psi) <= 0] = 0 # positive Psi = np.real(Psi) # and real-valued return Psi else: sys.exit("No suitable problem type specified in input_data['problemType'].") def proxShearlet(input_data, Psi, gamma): """ Implements the thresholding of shearlet coefficients for different kinds of objects (phase, amplitude, mixed and real objects) with general thresholding functions specified in the retrieveProximityName function. If the object is, e.g., a phase object, the shearlet decomposition and the thresholding is applide to the phase, e.g. to np.angle(Psi). Similarly this is done for mixed or amplitude objects. This can be combined with a range constraint (such as negativity for the phase) which will be applied before the shearlet decomposition. This is mathematically not completely consistent but makes sense in practice. Input: input_data Input_data structure. Psi Object / wave function the prox operator is applied to. gamma Prox operator parameter (step length), threshold parameter. """ shrinkageOperator = retrieveShrinkageName(input_data) if input_data["problemType"] == "phase": PsiPhase = np.angle(Psi) if "range" in input_data["problem"]["constraint"]: if input_data["problem"]["constraint"]["range"] == "positive": PsiPhase [ PsiPhase <= 0 ] = 0 else: PsiPhase [ PsiPhase >= 0 ] = 0 else: PsiPhase[ PsiPhase >= 0 ] = 0 coefficientsPhase = pySL.SLsheardec2D(PsiPhase, input_data["system"]) coefficientsPhase = eval(shrinkageOperator+"(input_data,coefficientsPhase, gamma)") PsiPhase = pySL.SLshearrec2D(coefficientsPhase, input_data["system"]) return np.exp(1j*PsiPhase) elif input_data["problemType"] == "amplitude": if input_data["problem"]["constraint"]["name"] == "shearlet thresholding and range": Psi [ Psi >= 1 ] = 1 PsiAmplitude = np.log(np.real(Psi)) coefficientsAmplitude = pySL.SLsheardec2D(PsiAmplitude, input_data["system"]) coefficientsAmplitude = eval(shrinkageOperator+"(input_data, coefficientsAmplitude, gamma)") PsiAmplitude = pySL.SLshearrec2D(coefficientsAmplitude, input_data["system"]) return np.exp(PsiAmplitude) elif input_data["problemType"] == "mixed": PsiAmplitude = np.abs(Psi) PsiPhase = np.angle(Psi) coefficientsAmplitude = pySL.SLsheardec2D(PsiAmplitude, input_data["system"]) coefficientsPhase = pySL.SLsheardec2D(PsiPhase, input_data["system"]) coefficientsAmplitude = eval(shrinkageOperator+"(input_data, coefficientsAmplitude, gamma)") coefficientsPhase = eval(shrinkageOperator+"(input_data, coefficientsPhase, gamma)") PsiAmplitude = pySL.SLshearrec2D(coefficientsAmplitude, input_data["system"]) PsiPhase = pySL.SLshearrec2D(coefficientsPhase, input_data["system"]) if input_data["constraint"]["name"] == "shearlet thresholding and range": PsiAmplitude[ PsiAmplitude >= 1] = 1 PsiPhase[ PsiPhase >= 0 ] = 0 # extend this for specific range information return PsiAmplitude * np.exp(1j * PsiPhase) elif input_data["problemType"] == "real": PsiReal = np.real(Psi) coefficientsReal = pySL.SLsheardec2D(PsiReal, input_data["system"]) coefficientsReal = eval(shrinkageOperator+"(input_data, coefficientsReal, gamma)") PsiReal = pySL.SLshearrec2D(coefficientsReal, input_data["system"]) if input_data["problem"]["constraint"]["name"] == "shearlet thresholding and range": PsiReal = np.real(PsiReal) PsiReal[ PsiReal <= 0 ] = 0 return PsiReal else: sys.exit("No suitable problem type specified in input_data.problem.type. See proxShearlet in pyprProxOperators.py for implementation details.") # Implements support constraints for different objects. # Input: input_data - General input_data structure. # Psi - Object to be projected. # # Output: outputPsi - Object after projection onto support constraint # # TODO: Figure out if we want to use np.abs or np.log for the amplitude part. # TODO: It porbably only differs in the scaling which should not matter for # TODO: support constraint but will for something like thresholding. # # TODO: Would it make sense to have different supports in mixed objects for phase/ampl.? # def proxSupportConstraint(input_data, Psi): """ Implements support constraints for different objects. Input: input_data Input_data structure. Psi Object / wave function to be projected. """ if input_data["problemType"] == "phase": PsiPhase = np.angle(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsiPhase = np.zeros(PsiPhase.shape) outPsiPhase = PsiPhase[suppPsi] return np.exp(1j * outPsiPhase) elif input_data["problemType"] == "amplitude": PsiAmpl = np.log(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsi = np.zeros(Psi.shape) outPsi[suppPsi] = PsiAmpl[suppPsi] return np.exp(outPsi) elif input_data["problemType"] == "mixed": PsiPhase = np.angle(Psi) PsiAmpl = np.abs(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsiPhase = np.zeros(Psi.shape) outPsiAmpl = np.zeros(Psi.shape) outPsiPhase[suppPsi] = PsiPhase[suppPsi] outPsiAmpl[suppPsi] = PsiAmpl[suppPsi] return outPsiAmpl * np.exp(1j * outPsiPhase) elif input_data["problemType"] == "real": suppPsi = input_data["problem"]["constraint"]["support"] outPsi = np.zeros(Psi.shape) outPsi[suppPsi] = Psi[suppPsi] return outPsi else: sys.exit("Error: No suitable problem type specified in input_data['problemType'']. See proxSupportConstraint in pyprProxOperators.py for details.") # Implements support + range constraints for different objects. # Input: input_data - General input_data structure. # Psi - Object to be projected. # # Output: outputPsi - Object after projection onto support + range constraint # # TODO: Figure out if we want to use np.abs or np.log for the amplitude part. # TODO: It porbably only differs in the scaling which should not matter for # TODO: support constraint but will for something like thresholding or range constraint. # # TODO: Would it make sense to have different supports in mixed objects for phase/ampl.? # def proxSupportRangeConstraint(input_data, Psi): if input_data["problemType"] == "phase": PsiPhase = np.angle(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsiPhase = np.zeros(PsiPhase.shape) # If no range type is specified we go with negative phase. if "range" in input_data["problem"]["constraint"]: if input_data["problem"]["constraint"]["range"] == "positive": PsiPhase[PsiPhase <= 0] = 0 else: PsiPhase[PsiPhase >= 0] = 0 else: PsiPhase[PsiPhase >= 0] = 0 outPsiPhase[suppPsi] = PsiPhase[suppPsi] return np.exp(1j * outPsiPhase) elif input_data["problemType"] == "amplitude": PsiAmpl = np.log(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsi = np.zeros(Psi.shape) # TODO: Extend to different amplitude constraints? Does that make sense? PsiAmpl [ PsiAmpl >= 1 ] = 1 outPsi[suppPsi] = PsiAmpl[suppPsi] return np.exp(outPsi) elif input_data["problemType"] == "mixed": PsiPhase = np.angle(Psi) PsiAmpl = np.abs(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsiPhase = np.zeros(Psi.shape) outPsiAmpl = np.zeros(Psi.shape) # if no range type is specified we go with negative phase if "range" in input_data["problem"]["constraint"]: if input_data["problem"]["constraint"]["range"] == "positive": PsiPhase[PsiPhase <= 0] = 0 else: PsiPhase[PsiPhase >= 0] = 0 else: PsiPhase[PsiPhase >= 0] = 0 # TODO: Extend to different amplitude constraints? Does that make sense? PsiAmpl[PsiAmpl >= 1] = 1 outPsiPhase[suppPsi] = PsiPhase[suppPsi] outPsiAmpl[suppPsi] = PsiAmpl[suppPsi] return outPsiAmpl * np.exp(1j * outPsiPhase) elif input_data["problemType"] == "real": suppPsi = input_data["problem"]["constraint"]["support"] outPsi = np.zeros(Psi.shape) Psi = np.real(Psi) # TODO: This is the non-negativitiy. Could this be extended in a meaningful way? Psi[Psi <= 0] = 0 outPsi[suppPsi] = Psi[suppPsi] return outPsi else: sys.exit("Error: No suitable problem type specified in input_data['problemType'']. See proxSupportRangeConstraint in pyprProxOperators.py for details.")
Functions
def approximateMagnitudeProjection(
input_data, Psi)
Calculates an approximate projection onto a regularized set of measurements. This method can easily be extend to use input_data.noise.type and then calculate different error distances accordingly.
Input:
input_data: Input_data structure. Psi: Current iterate / wave function.
def approximateMagnitudeProjection(input_data, Psi): """ Calculates an approximate projection onto a regularized set of measurements. This method can easily be extend to use input_data.noise.type and then calculate different error distances accordingly. Input: input_data: Input_data structure. Psi: Current iterate / wave function. """ if calculateKLDistance(input_data, Psi) >= input_data["noise"]["error"]: outputWave = magnitudeProjection(input_data, Psi) else: outputWave = Psi return outputWave
def constraintProximityOperator(
input_data, Psi, gamma)
Applies Projection/Proximity Operator depending on the given constraint.
Input:
input_data Input_data structure. Psi Object to be projected. gamma Parameter (only for proximity operators)
def constraintProximityOperator(input_data, Psi, gamma): """ Applies Projection/Proximity Operator depending on the given constraint. Input: input_data Input_data structure. Psi Object to be projected. gamma Parameter (only for proximity operators) """ proximityOperatorName = retrieveProximityName(input_data) if (proximityOperatorName == "proxSupportConstraint") or (proximityOperatorName == "proxSupportRangeConstraint") or (proximityOperatorName == "proxRangeConstraint"): outputPsi = eval(proximityOperatorName+"(input_data, Psi)") elif proximityOperatorName == "proxShearlet": #outputPsi = eval(proximityOperatorName, input_data, Psi, gamma) outputPsi = eval(proximityOperatorName+"(input_data, Psi, gamma)") else: sys.exit("The provided proximityOperatorName " + proximityOperatorName + " is not supported." + "Check constraintProximityOperator in pyprProxOperators.py for details.") return outputPsi
def magnitudeProjection(
input_data, Psi)
Calculates the projection onto a magnitude constraint set.
Input:
input_data Input_data structure. Psi Object / wave function to be projected.
def magnitudeProjection(input_data, Psi): """ Calculates the projection onto a magnitude constraint set. Input: input_data Input_data structure. Psi Object / wave function to be projected. """ propagatedWave = wavePropagator(input_data, Psi, 1) projectedWave = MagProj(input_data["problem"]["data"], propagatedWave) outputWave = wavePropagator(input_data, projectedWave, -1) return outputWave
def proxRangeConstraint(
input_data, Psi)
Implements range constraint for different types of objects, e.h. positivity, negative phase, and others.
Input:
input_data Input_data structure. Psi Object / wave function to be projected.
def proxRangeConstraint(input_data, Psi): """ Implements range constraint for different types of objects, e.h. positivity, negative phase, and others. Input: input_data Input_data structure. Psi Object / wave function to be projected. """ if input_data["problemType"] == "phase": PsiPhase = np.angle(Psi) if "range" not in input_data["problem"]["constraint"]: # no info, hence negativitiy assumed. PsiPhase [PsiPhase >= 0] = 0 else: if input_data["problem"]["constraint"]["range"] == "positive": PsiPhase [PsiPhase <= 0] = 0 return np.exp(1j * PsiPhase) elif input_data["problemType"] == "amplitude": PsiAmplitude = np.abs(Psi) PsiAmplitude[ PsiAmplitude >= 1 ] = 1 return PsiAmplitude elif input_data["problemType"]["mixed"]: PsiPhase = np.angle(Psi) PsiAmplitude = np.abs(Psi) if "range" not in input_data["problem"]["constraint"]: PsiPhase[ PsiPhase <= 0 ] = 0 PsiAmplitude[ PsiAmplitude >= 1 ] = 1 else: if input_data["problem"]["constraint"]["range"] == "positive": PsiPhase[ PsiPhase >= 0 ] = 0 PsiAmplitude[ PsiAmplitude >= 1] = 1 return PsiAmplitude * np.exp(1j * PsiPhase) elif input_data["problemType"] == "real": if "range" not in input_data["problem"]["constraint"]: Psi[np.real(Psi) <= 0] = 0 # positive Psi = np.real(Psi) # and real-valued return Psi else: sys.exit("No suitable problem type specified in input_data['problemType'].")
def proxShearlet(
input_data, Psi, gamma)
Implements the thresholding of shearlet coefficients for different kinds of objects (phase, amplitude, mixed and real objects) with general thresholding functions specified in the retrieveProximityName function.
If the object is, e.g., a phase object, the shearlet decomposition and the thresholding is applide to the phase, e.g. to np.angle(Psi). Similarly this is done for mixed or amplitude objects.
This can be combined with a range constraint (such as negativity for the phase) which will be applied before the shearlet decomposition. This is mathematically not completely consistent but makes sense in practice.
Input:
input_data Input_data structure. Psi Object / wave function the prox operator is applied to. gamma Prox operator parameter (step length), threshold parameter.
def proxShearlet(input_data, Psi, gamma): """ Implements the thresholding of shearlet coefficients for different kinds of objects (phase, amplitude, mixed and real objects) with general thresholding functions specified in the retrieveProximityName function. If the object is, e.g., a phase object, the shearlet decomposition and the thresholding is applide to the phase, e.g. to np.angle(Psi). Similarly this is done for mixed or amplitude objects. This can be combined with a range constraint (such as negativity for the phase) which will be applied before the shearlet decomposition. This is mathematically not completely consistent but makes sense in practice. Input: input_data Input_data structure. Psi Object / wave function the prox operator is applied to. gamma Prox operator parameter (step length), threshold parameter. """ shrinkageOperator = retrieveShrinkageName(input_data) if input_data["problemType"] == "phase": PsiPhase = np.angle(Psi) if "range" in input_data["problem"]["constraint"]: if input_data["problem"]["constraint"]["range"] == "positive": PsiPhase [ PsiPhase <= 0 ] = 0 else: PsiPhase [ PsiPhase >= 0 ] = 0 else: PsiPhase[ PsiPhase >= 0 ] = 0 coefficientsPhase = pySL.SLsheardec2D(PsiPhase, input_data["system"]) coefficientsPhase = eval(shrinkageOperator+"(input_data,coefficientsPhase, gamma)") PsiPhase = pySL.SLshearrec2D(coefficientsPhase, input_data["system"]) return np.exp(1j*PsiPhase) elif input_data["problemType"] == "amplitude": if input_data["problem"]["constraint"]["name"] == "shearlet thresholding and range": Psi [ Psi >= 1 ] = 1 PsiAmplitude = np.log(np.real(Psi)) coefficientsAmplitude = pySL.SLsheardec2D(PsiAmplitude, input_data["system"]) coefficientsAmplitude = eval(shrinkageOperator+"(input_data, coefficientsAmplitude, gamma)") PsiAmplitude = pySL.SLshearrec2D(coefficientsAmplitude, input_data["system"]) return np.exp(PsiAmplitude) elif input_data["problemType"] == "mixed": PsiAmplitude = np.abs(Psi) PsiPhase = np.angle(Psi) coefficientsAmplitude = pySL.SLsheardec2D(PsiAmplitude, input_data["system"]) coefficientsPhase = pySL.SLsheardec2D(PsiPhase, input_data["system"]) coefficientsAmplitude = eval(shrinkageOperator+"(input_data, coefficientsAmplitude, gamma)") coefficientsPhase = eval(shrinkageOperator+"(input_data, coefficientsPhase, gamma)") PsiAmplitude = pySL.SLshearrec2D(coefficientsAmplitude, input_data["system"]) PsiPhase = pySL.SLshearrec2D(coefficientsPhase, input_data["system"]) if input_data["constraint"]["name"] == "shearlet thresholding and range": PsiAmplitude[ PsiAmplitude >= 1] = 1 PsiPhase[ PsiPhase >= 0 ] = 0 # extend this for specific range information return PsiAmplitude * np.exp(1j * PsiPhase) elif input_data["problemType"] == "real": PsiReal = np.real(Psi) coefficientsReal = pySL.SLsheardec2D(PsiReal, input_data["system"]) coefficientsReal = eval(shrinkageOperator+"(input_data, coefficientsReal, gamma)") PsiReal = pySL.SLshearrec2D(coefficientsReal, input_data["system"]) if input_data["problem"]["constraint"]["name"] == "shearlet thresholding and range": PsiReal = np.real(PsiReal) PsiReal[ PsiReal <= 0 ] = 0 return PsiReal else: sys.exit("No suitable problem type specified in input_data.problem.type. See proxShearlet in pyprProxOperators.py for implementation details.")
def proxSupportConstraint(
input_data, Psi)
Implements support constraints for different objects.
Input:
input_data Input_data structure. Psi Object / wave function to be projected.
def proxSupportConstraint(input_data, Psi): """ Implements support constraints for different objects. Input: input_data Input_data structure. Psi Object / wave function to be projected. """ if input_data["problemType"] == "phase": PsiPhase = np.angle(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsiPhase = np.zeros(PsiPhase.shape) outPsiPhase = PsiPhase[suppPsi] return np.exp(1j * outPsiPhase) elif input_data["problemType"] == "amplitude": PsiAmpl = np.log(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsi = np.zeros(Psi.shape) outPsi[suppPsi] = PsiAmpl[suppPsi] return np.exp(outPsi) elif input_data["problemType"] == "mixed": PsiPhase = np.angle(Psi) PsiAmpl = np.abs(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsiPhase = np.zeros(Psi.shape) outPsiAmpl = np.zeros(Psi.shape) outPsiPhase[suppPsi] = PsiPhase[suppPsi] outPsiAmpl[suppPsi] = PsiAmpl[suppPsi] return outPsiAmpl * np.exp(1j * outPsiPhase) elif input_data["problemType"] == "real": suppPsi = input_data["problem"]["constraint"]["support"] outPsi = np.zeros(Psi.shape) outPsi[suppPsi] = Psi[suppPsi] return outPsi else: sys.exit("Error: No suitable problem type specified in input_data['problemType'']. See proxSupportConstraint in pyprProxOperators.py for details.")
def proxSupportRangeConstraint(
input_data, Psi)
def proxSupportRangeConstraint(input_data, Psi): if input_data["problemType"] == "phase": PsiPhase = np.angle(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsiPhase = np.zeros(PsiPhase.shape) # If no range type is specified we go with negative phase. if "range" in input_data["problem"]["constraint"]: if input_data["problem"]["constraint"]["range"] == "positive": PsiPhase[PsiPhase <= 0] = 0 else: PsiPhase[PsiPhase >= 0] = 0 else: PsiPhase[PsiPhase >= 0] = 0 outPsiPhase[suppPsi] = PsiPhase[suppPsi] return np.exp(1j * outPsiPhase) elif input_data["problemType"] == "amplitude": PsiAmpl = np.log(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsi = np.zeros(Psi.shape) # TODO: Extend to different amplitude constraints? Does that make sense? PsiAmpl [ PsiAmpl >= 1 ] = 1 outPsi[suppPsi] = PsiAmpl[suppPsi] return np.exp(outPsi) elif input_data["problemType"] == "mixed": PsiPhase = np.angle(Psi) PsiAmpl = np.abs(Psi) suppPsi = input_data["problem"]["constraint"]["support"] outPsiPhase = np.zeros(Psi.shape) outPsiAmpl = np.zeros(Psi.shape) # if no range type is specified we go with negative phase if "range" in input_data["problem"]["constraint"]: if input_data["problem"]["constraint"]["range"] == "positive": PsiPhase[PsiPhase <= 0] = 0 else: PsiPhase[PsiPhase >= 0] = 0 else: PsiPhase[PsiPhase >= 0] = 0 # TODO: Extend to different amplitude constraints? Does that make sense? PsiAmpl[PsiAmpl >= 1] = 1 outPsiPhase[suppPsi] = PsiPhase[suppPsi] outPsiAmpl[suppPsi] = PsiAmpl[suppPsi] return outPsiAmpl * np.exp(1j * outPsiPhase) elif input_data["problemType"] == "real": suppPsi = input_data["problem"]["constraint"]["support"] outPsi = np.zeros(Psi.shape) Psi = np.real(Psi) # TODO: This is the non-negativitiy. Could this be extended in a meaningful way? Psi[Psi <= 0] = 0 outPsi[suppPsi] = Psi[suppPsi] return outPsi else: sys.exit("Error: No suitable problem type specified in input_data['problemType'']. See proxSupportRangeConstraint in pyprProxOperators.py for details.")