Beispiel #1
0
    def testZernikeAnnularGradDxy(self):

        surfGrad = ZernikeAnnularGrad(
            self.zerCoef, self.xx, self.yy, self.obscuration, "dxy"
        )

        self._checkAnsWithFile(surfGrad, "annularZernikeGradDxy.txt")
Beispiel #2
0
    def _aperture2image(self, inst, algo, zcCol, lutx, luty, projSamples,
                        model):
        """Calculate the x, y-coordinate on the focal plane and the related
        Jacobian matrix.

        Parameters
        ----------
        inst : Instrument
            Instrument to use.
        algo : Algorithm
            Algorithm to solve the Poisson's equation. It can by done by the
            fast Fourier transform or serial expansion.
        zcCol : numpy.ndarray
            Coefficients of optical basis. It is Zernike polynomials in the
            baseline.
        lutx : numpy.ndarray
            X-coordinate on pupil plane.
        luty : numpy.ndarray
            Y-coordinate on pupil plane.
        projSamples : int
            Dimension of projected image. This value considers the
            magnification ratio of donut image.
        model : str
            Optical model. It can be "paraxial", "onAxis", or "offAxis".

        Returns
        -------
        numpy.ndarray
            X coordinate on the focal plane.
        numpy.ndarray
            Y coordinate on the focal plane.
        numpy.ndarray
            Jacobian matrix between the pupil and focal plane.
        """

        # Get the radius: R = D/2
        R = inst.getApertureDiameter() / 2

        # Calculate C = -f(f-l)/l/R^2. This is for the calculation of reduced
        # coordinate.
        defocalDisOffset = inst.getDefocalDisOffset()
        if self.defocalType == DefocalType.Intra:
            l = defocalDisOffset
        elif self.defocalType == DefocalType.Extra:
            l = -defocalDisOffset

        focalLength = inst.getFocalLength()
        myC = -focalLength * (focalLength - l) / l / R**2

        # Get the functions to do the off-axis correction by numerical fitting
        # Order to do the off-axis correction. The order is 10 now.
        offAxisPolyOrder = algo.getOffAxisPolyOrder()
        polyFunc = self._getFunction("poly%d_2D" % offAxisPolyOrder)
        polyGradFunc = self._getFunction("poly%dGrad" % offAxisPolyOrder)

        # Calculate the distance to center
        lutr = np.sqrt(lutx**2 + luty**2)

        # Calculated the extended ring radius (delta r), which is to extended
        # the available pupil area.
        # 1 pixel larger than projected pupil. No need to be EF-like, anything
        # outside of this will be masked off by the computational mask
        sensorFactor = inst.getSensorFactor()
        onepixel = 1 / (projSamples / 2 / sensorFactor)

        # Get the index that the point is out of the range of extended pupil
        obscuration = inst.getObscuration()
        idxout = (lutr > 1 + onepixel) | (lutr < obscuration - onepixel)

        # Define the element to be NaN if it is out of range
        lutx[idxout] = np.nan
        luty[idxout] = np.nan

        # Get the index in the extended area of outer boundary with the width
        # of onepixel
        idxbound = (lutr <= 1 + onepixel) & (lutr > 1)

        # Calculate the extended x, y-coordinate (x' = x/r*r', r'=1)
        lutx[idxbound] = lutx[idxbound] / lutr[idxbound]
        luty[idxbound] = luty[idxbound] / lutr[idxbound]

        # Get the index in the extended area of inner boundary with the width
        # of onepixel
        idxinbd = (lutr < obscuration) & (lutr > obscuration - onepixel)

        # Calculate the extended x, y-coordinate (x' = x/r*r', r'=obscuration)
        lutx[idxinbd] = lutx[idxinbd] / lutr[idxinbd] * obscuration
        luty[idxinbd] = luty[idxinbd] / lutr[idxinbd] * obscuration

        # Get the corrected x, y-coordinate on focal plane (lutxp, lutyp)
        if model == "paraxial":
            # No correction is needed in "paraxial" model
            lutxp = lutx
            lutyp = luty

        elif model == "onAxis":

            # Calculate F(x, y) = m * sqrt(f^2-R^2) / sqrt(f^2-(x^2+y^2)*R^2)
            # m is the mask scaling factor
            myA2 = (focalLength**2 - R**2) / (focalLength**2 - lutr**2 * R**2)

            # Put the unphysical value as NaN
            myA = myA2.copy()
            idx = myA < 0
            myA[idx] = np.nan
            myA[~idx] = np.sqrt(myA2[~idx])

            # Mask scaling factor (for fast beam)
            maskScalingFactor = algo.getMaskScalingFactor()

            # Calculate the x, y-coordinate on focal plane
            # x' = F(x,y)*x + C*(dW/dx), y' = F(x,y)*y + C*(dW/dy)
            lutxp = maskScalingFactor * myA * lutx
            lutyp = maskScalingFactor * myA * luty

        elif model == "offAxis":

            # Get the coefficient of polynomials for off-axis correction
            tt = self.offAxisOffset

            cx = (self.offAxisCoeff[0, :] - self.offAxisCoeff[2, :]) * (
                tt + l) / (2 * tt) + self.offAxisCoeff[2, :]
            cy = (self.offAxisCoeff[1, :] - self.offAxisCoeff[3, :]) * (
                tt + l) / (2 * tt) + self.offAxisCoeff[3, :]

            # This will be inverted back by typesign later on.
            # We do the inversion here to make the (x,y)->(x',y') equations has
            # the same form as the paraxial case.
            cx = np.sign(l) * cx
            cy = np.sign(l) * cy

            # Do the orthogonalization: x'=1/sqrt(2)*(x+y), y'=1/sqrt(2)*(x-y)
            # Calculate the rotation angle for the orthogonalization
            fieldDist = self._getFieldDistFromOrigin()
            costheta = (self.fieldX + self.fieldY) / fieldDist / np.sqrt(2)
            if costheta > 1:
                costheta = 1
            elif costheta < -1:
                costheta = -1

            sintheta = np.sqrt(1 - costheta**2)
            if self.fieldY < self.fieldX:
                sintheta = -sintheta

            # Create the pupil grid in off-axis model. This gives the
            # x,y-coordinate in the extended ring area defined by the parameter
            # of onepixel.

            # Get the mask-related parameters
            maskCa, maskRa, maskCb, maskRb = self._interpMaskParam(
                self.fieldX, self.fieldY, inst.getMaskOffAxisCorr())

            lutx, luty = self._createPupilGrid(
                lutx,
                luty,
                onepixel,
                maskCa,
                maskCb,
                maskRa,
                maskRb,
                self.fieldX,
                self.fieldY,
            )

            # Calculate the x, y-coordinate on focal plane

            # First rotate back to reference orientation
            lutx0 = lutx * costheta + luty * sintheta
            luty0 = -lutx * sintheta + luty * costheta

            # Use the mapping at reference orientation
            lutxp0 = polyFunc(cx, lutx0, y=luty0)
            lutyp0 = polyFunc(cy, lutx0, y=luty0)

            # Rotate back to focal plane
            lutxp = lutxp0 * costheta - lutyp0 * sintheta
            lutyp = lutxp0 * sintheta + lutyp0 * costheta

            # Zemax data are in mm, therefore 1000
            dimOfDonut = inst.getDimOfDonutOnSensor()
            pixelSize = inst.getCamPixelSize()
            reduced_coordi_factor = 1e-3 / (dimOfDonut / 2 * pixelSize /
                                            sensorFactor)

            # Reduced coordinates, so that this can be added with the dW/dz
            lutxp = lutxp * reduced_coordi_factor
            lutyp = lutyp * reduced_coordi_factor

        else:
            print("Wrong optical model type in compensate. \n")
            return

        # Obscuration of annular aperture
        zobsR = algo.getObsOfZernikes()

        # Calculate the x, y-coordinate on focal plane
        # x' = F(x,y)*x + C*(dW/dx), y' = F(x,y)*y + C*(dW/dy)

        # In Model basis (zer: Zernike polynomials)
        if zcCol.ndim == 1:
            lutxp = lutxp + myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR,
                                                     "dx")
            lutyp = lutyp + myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR,
                                                     "dy")

        # Make the sign to be consistent
        if self.defocalType == DefocalType.Extra:
            lutxp = -lutxp
            lutyp = -lutyp

        # Calculate the Jacobian matrix
        # In Model basis (zer: Zernike polynomials)
        if zcCol.ndim == 1:
            if model == "paraxial":
                J = (1 + myC *
                     ZernikeAnnularJacobian(zcCol, lutx, luty, zobsR, "1st") +
                     myC**2 *
                     ZernikeAnnularJacobian(zcCol, lutx, luty, zobsR, "2nd"))

            elif model == "onAxis":
                xpox = maskScalingFactor * myA * (
                    1 + lutx**2 * R**2.0 / (focalLength**2 - R**2 * lutr**2)
                ) + myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR, "dx2")

                ypoy = maskScalingFactor * myA * (
                    1 + luty**2 * R**2.0 / (focalLength**2 - R**2 * lutr**2)
                ) + myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR, "dy2")

                xpoy = maskScalingFactor * myA * lutx * luty * R**2 / (
                    focalLength**2 - R**2 * lutr**2
                ) + myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR, "dxy")

                ypox = xpoy

                J = xpox * ypoy - xpoy * ypox

            elif model == "offAxis":
                xp0ox = (polyGradFunc(cx, lutx0, luty0, "dx") * costheta -
                         polyGradFunc(cx, lutx0, luty0, "dy") * sintheta)

                yp0ox = (polyGradFunc(cy, lutx0, luty0, "dx") * costheta -
                         polyGradFunc(cy, lutx0, luty0, "dy") * sintheta)

                xp0oy = (polyGradFunc(cx, lutx0, luty0, "dx") * sintheta +
                         polyGradFunc(cx, lutx0, luty0, "dy") * costheta)

                yp0oy = (polyGradFunc(cy, lutx0, luty0, "dx") * sintheta +
                         polyGradFunc(cy, lutx0, luty0, "dy") * costheta)

                xpox = (xp0ox * costheta - yp0ox * sintheta
                        ) * reduced_coordi_factor + myC * ZernikeAnnularGrad(
                            zcCol, lutx, luty, zobsR, "dx2")

                ypoy = (xp0oy * sintheta + yp0oy * costheta
                        ) * reduced_coordi_factor + myC * ZernikeAnnularGrad(
                            zcCol, lutx, luty, zobsR, "dy2")

                temp = myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR,
                                                "dxy")

                # if temp==0,xpoy doesn't need to be symmetric about x=y
                xpoy = (xp0oy * costheta -
                        yp0oy * sintheta) * reduced_coordi_factor + temp

                # xpoy-flipud(rot90(ypox))==0 is true
                ypox = (xp0ox * sintheta +
                        yp0ox * costheta) * reduced_coordi_factor + temp

                J = xpox * ypoy - xpoy * ypox

        return lutxp, lutyp, J
Beispiel #3
0
    def __solvePoissonEq(self, inst, I1, I2, iOutItr=0):
        """

        Solve the Poisson's equation by Fourier transform (differential) or serial expansion
        (integration).

        There is no convergence for fft actually. Need to add the difference comparison and
        Xa method. Need to discuss further for this.

        Arguments:
            inst {[Instrument]} -- Instrument to use.
            I1 {[Image]} -- Intra- or extra-focal image.
            I2 {[Image]} -- Intra- or extra-focal image.

        Keyword Arguments:
            iOutItr {[int]} -- ith number of outer loop iteration which is important
                               in "fft" algorithm (default: {0}).

        Returns:
            [float] -- Coefficients of normal/ annular Zernike polynomials.
            [float] -- Estimated wavefront.
        """

        # Calculate the aperature pixel size
        apertureDiameter = inst.parameter["apertureDiameter"]
        sensorFactor = inst.parameter["sensorFactor"]
        sensorSamples = inst.parameter["sensorSamples"]
        aperturePixelSize = apertureDiameter * sensorFactor / sensorSamples

        # Calculate the differential Omega
        dOmega = aperturePixelSize**2

        # Solve the Poisson's equation based on the type of algorithm
        numTerms = self.parameter["numTerms"]
        zobsR = self.parameter["zobsR"]
        PoissonSolver = self.parameter["PoissonSolver"]
        if (PoissonSolver == "fft"):

            # Use the differential method by fft to solve the Poisson's equation

            # Parameter to determine the threshold of calculating I0.
            sumclipSequence = self.parameter["sumclipSequence"]
            cliplevel = sumclipSequence[iOutItr]

            # Generate the v, u-coordinates on pupil plane
            padDim = self.parameter["padDim"]
            v, u = np.mgrid[-0.5 / aperturePixelSize:0.5 /
                            aperturePixelSize:1. / padDim / aperturePixelSize,
                            -0.5 / aperturePixelSize:0.5 /
                            aperturePixelSize:1. / padDim / aperturePixelSize]

            # Show the threshold and pupil coordinate information
            if (self.debugLevel >= 3):
                print("iOuter=%d, cliplevel=%4.2f" % (iOutItr, cliplevel))
                print(v.shape)

            # Calculate the const of fft: FT{Delta W} = -4*pi^2*(u^2+v^2) * FT{W}
            u2v2 = -4 * (np.pi**2) * (u * u + v * v)

            # Set origin to Inf to result in 0 at origin after filtering
            ctrIdx = int(np.floor(padDim / 2.0))
            u2v2[ctrIdx, ctrIdx] = np.inf

            # Calculate the wavefront signal
            Sini = self.__createSignal(inst, I1, I2, cliplevel)

            # Find the just-outside and just-inside indices of a ring in pixels
            # This is for the use in setting dWdn = 0
            boundaryT = self.parameter["boundaryT"]

            struct = generate_binary_structure(2, 1)
            struct = iterate_structure(struct, boundaryT)

            ApringOut = np.logical_xor(
                binary_dilation(self.pMask, structure=struct),
                self.pMask).astype(int)
            ApringIn = np.logical_xor(
                binary_erosion(self.pMask, structure=struct),
                self.pMask).astype(int)

            bordery, borderx = np.nonzero(ApringOut)

            # Put the signal in boundary (since there's no existing Sestimate, S just equals self.S
            # as the initial condition of SCF
            S = Sini.copy()
            for jj in range(int(self.parameter["innerItr"])):

                # Calculate FT{S}
                SFFT = np.fft.fftshift(np.fft.fft2(np.fft.fftshift(S)))

                # Calculate W by W=IFT{ FT{S}/(-4*pi^2*(u^2+v^2)) }
                W = np.fft.fftshift(
                    np.fft.irfft2(np.fft.fftshift(SFFT / u2v2), s=S.shape))

                # Estimate the wavefront (includes zeroing offset & masking to the aperture size)

                # Take the estimated wavefront
                West = extractArray(W, sensorSamples)

                # Calculate the offset
                offset = West[self.pMask == 1].mean()
                West = West - offset
                West[self.pMask == 0] = 0

                # Set dWestimate/dn = 0 around boundary
                WestdWdn0 = West.copy()

                # Do a 3x3 average around each border pixel, including only those pixels
                # inside the aperture
                for ii in range(len(borderx)):
                    reg = West[borderx[ii] - boundaryT:borderx[ii] +
                               boundaryT + 1, bordery[ii] -
                               boundaryT:bordery[ii] + boundaryT + 1]

                    intersectIdx = ApringIn[borderx[ii] -
                                            boundaryT:borderx[ii] + boundaryT +
                                            1, bordery[ii] -
                                            boundaryT:bordery[ii] + boundaryT +
                                            1]

                    WestdWdn0[borderx[ii], bordery[ii]] = reg[np.nonzero(
                        intersectIdx)].mean()

                # Take Laplacian to find sensor signal estimate (Delta W = S)
                del2W = laplace(WestdWdn0) / dOmega

                # Extend the dimension of signal to the order of 2 for "fft" to use
                Sest = padArray(del2W, padDim)

                # Put signal back inside boundary, leaving the rest of Sestimate
                Sest[self.pMaskPad == 1] = Sini[self.pMaskPad == 1]

                # Need to recheck this condition
                S = Sest

            # Define the estimated wavefront
            # self.West = West.copy()

            # Calculate the coefficient of normal/ annular Zernike polynomials
            if (self.parameter["compMode"] == "zer"):
                zc = ZernikeMaskedFit(West, inst.xSensor, inst.ySensor,
                                      numTerms, self.pMask, zobsR)
            else:
                zc = np.zeros(numTerms)

        elif (PoissonSolver == "exp"):

            # Use the integration method by serial expansion to solve the Poisson's equation

            # Calculate I0 and dI
            I0, dI = self.__getdIandI(I1, I2)

            # Get the x, y coordinate in mask. The element outside mask is 0.
            xSensor = inst.xSensor * self.cMask
            ySensor = inst.ySensor * self.cMask

            # Create the F matrix and Zernike-related matrixes
            F = np.zeros(numTerms)
            dZidx = np.zeros([numTerms, sensorSamples, sensorSamples])
            dZidy = dZidx.copy()

            zcCol = np.zeros(numTerms)
            for ii in range(int(numTerms)):

                # Calculate the matrix for each Zk related component
                # Set the specific Zk cofficient to be 1 for the calculation
                zcCol[ii] = 1

                F[ii] = np.sum(dI * ZernikeAnnularEval(zcCol, xSensor, ySensor,
                                                       zobsR)) * dOmega
                dZidx[ii, :, :] = ZernikeAnnularGrad(zcCol, xSensor, ySensor,
                                                     zobsR, "dx")
                dZidy[ii, :, :] = ZernikeAnnularGrad(zcCol, xSensor, ySensor,
                                                     zobsR, "dy")

                # Set the specific Zk cofficient back to 0 to avoid interfering other Zk's calculation
                zcCol[ii] = 0

            # Calculate Mij matrix, need to check the stability of integration and symmetry later
            Mij = np.zeros([numTerms, numTerms])
            for ii in range(numTerms):
                for jj in range(numTerms):
                    Mij[ii, jj] = np.sum(I0 * (
                        dZidx[ii, :, :].squeeze() * dZidx[jj, :, :].squeeze() +
                        dZidy[ii, :, :].squeeze() * dZidy[jj, :, :].squeeze()))
            Mij = dOmega / (apertureDiameter / 2.)**2 * Mij

            # Calculate dz
            focalLength = inst.parameter["focalLength"]
            offset = inst.parameter["offset"]
            dz = 2 * focalLength * (focalLength - offset) / offset

            # Define zc
            zc = np.zeros(numTerms)

            # Consider specific Zk terms only
            idx = [x - 1 for x in self.parameter["ZTerms"]]

            # Solve the equation: M*W = F => W = M^(-1)*F
            zc_tmp = np.linalg.lstsq(Mij[:, idx][idx], F[idx],
                                     rcond=None)[0] / dz
            zc[idx] = zc_tmp

            # Estimate the wavefront surface based on z4 - z22
            # z0 - z3 are set to be 0 instead
            West = ZernikeAnnularEval(np.concatenate(([0, 0, 0], zc[3:])),
                                      xSensor, ySensor, zobsR)

        return zc, West
    def __aperture2image(self, inst, algo, zcCol, lutx, luty, projSamples,
                         model):
        """
        
        Calculate the x, y-coordinate on the focal plane and the related Jacobian matrix.
        
        Arguments:
            inst {[Instrument]} -- Instrument to use.
            algo {[Algorithm]} -- Algorithm to solve the Poisson's equation. It can by done 
                                  by the fast Fourier transform or serial expansion.
            zcCol {[float]} -- Coefficients of optical basis. It is Zernike polynomials in the 
                               baseline.
            lutx {[float]} -- x-coordinate on pupil plane.
            luty {[float]} -- y-coordinate on pupil plane.
            projSamples {[int]} -- Dimension of projected image. This value considers the
                                   magnification ratio of donut image.
            model {[string]} -- Optical model. It can be "paraxial", "onAxis", or "offAxis".
        
        Returns:
            [float] -- x, y-coordinate on the focal plane.
            [float] -- Jacobian matrix between the pupil and focal plane.
        """

        # Get the radius: R = D/2
        R = inst.parameter["apertureDiameter"] / 2.0

        # Calculate C = -f(f-l)/l/R^2. This is for the calculation of reduced coordinate.
        if (self.atype == self.INTRA):
            l = inst.parameter["offset"]
        elif (self.atype == self.EXTRA):
            l = -inst.parameter["offset"]
        focalLength = inst.parameter["focalLength"]
        myC = -focalLength * (focalLength - l) / l / R**2

        # Get the functions to do the off-axis correction by numerical fitting
        # Order to do the off-axis correction. The order is 10 now.
        offAxisPolyOrder = algo.parameter["offAxisPolyOrder"]
        polyFunc = self.__getFunction("poly%d_2D" % offAxisPolyOrder)
        polyGradFunc = self.__getFunction("poly%dGrad" % offAxisPolyOrder)

        # Calculate the distance to center
        lutr = np.sqrt(lutx**2 + luty**2)

        # Calculated the extended ring radius (delta r), which is to extended the available
        # pupil area.
        # 1 pixel larger than projected pupil. No need to be EF-like, anything
        # outside of this will be masked off by the computational mask
        sensorFactor = inst.parameter["sensorFactor"]
        onepixel = 1 / (projSamples / 2 / sensorFactor)

        # Get the index that the point is out of the range of extended pupil
        obscuration = inst.parameter["obscuration"]
        idxout = (lutr > 1 + onepixel) | (lutr < obscuration - onepixel)

        # Define the element to be NaN if it is out of range
        lutx[idxout] = np.nan
        luty[idxout] = np.nan

        # Get the index in the extended area of outer boundary with the width of onepixel
        idxbound = (lutr <= 1 + onepixel) & (lutr > 1)

        # Calculate the extended x, y-coordinate (x' = x/r*r', r'=1)
        lutx[idxbound] = lutx[idxbound] / lutr[idxbound]
        luty[idxbound] = luty[idxbound] / lutr[idxbound]

        # Get the index in the extended area of inner boundary with the width of onepixel
        idxinbd = (lutr < obscuration) & (lutr > obscuration - onepixel)

        # Calculate the extended x, y-coordinate (x' = x/r*r', r'=obscuration)
        lutx[idxinbd] = lutx[idxinbd] / lutr[idxinbd] * obscuration
        luty[idxinbd] = luty[idxinbd] / lutr[idxinbd] * obscuration

        # Get the corrected x, y-coordinate on focal plane (lutxp, lutyp)
        if (model == "paraxial"):
            # No correction is needed in "paraxial" model
            lutxp = lutx
            lutyp = luty

        elif (model == "onAxis"):

            # Calculate F(x, y) = m * sqrt(f^2-R^2) / sqrt(f^2-(x^2+y^2)*R^2)
            # m is the mask scaling factor
            myA2 = (focalLength**2 - R**2) / (focalLength**2 - lutr**2 * R**2)

            # Put the unphysical value as NaN
            myA = myA2.copy()
            idx = (myA < 0)
            myA[idx] = np.nan
            myA[~idx] = np.sqrt(myA2[~idx])

            # Mask scaling factor (for fast beam)
            maskScalingFactor = algo.parameter["maskScalingFactor"]

            # Calculate the x, y-coordinate on focal plane
            # x' = F(x,y)*x + C*(dW/dx), y' = F(x,y)*y + C*(dW/dy)
            lutxp = maskScalingFactor * myA * lutx
            lutyp = maskScalingFactor * myA * luty

        elif (model == "offAxis"):

            # Get the coefficient of polynomials for off-axis correction
            tt = self.offAxisOffset

            cx = (self.offAxis_coeff[0, :] - self.offAxis_coeff[2, :]) * (tt+l)/(2*tt) + \
                    self.offAxis_coeff[2, :]
            cy = (self.offAxis_coeff[1, :] - self.offAxis_coeff[3, :]) * (tt+l)/(2*tt) + \
                    self.offAxis_coeff[3, :]

            # This will be inverted back by typesign later on.
            # We do the inversion here to make the (x,y)->(x',y') equations has
            # the same form as the paraxial case.
            cx = np.sign(l) * cx
            cy = np.sign(l) * cy

            # Do the orthogonalization: x'=1/sqrt(2)*(x+y), y'=1/sqrt(2)*(x-y)
            # Calculate the rotation angle for the orthogonalization
            costheta = (self.fieldX + self.fieldY) / self.fldr / np.sqrt(2)
            if (costheta > 1):
                costheta = 1
            elif (costheta < -1):
                costheta = -1

            sintheta = np.sqrt(1 - costheta**2)
            if (self.fieldY < self.fieldX):
                sintheta = -sintheta

            # Create the pupil grid in off-axis model. This gives the x,y-coordinate
            # in the extended ring area defined by the parameter of onepixel.

            # Get the mask-related parameters
            maskCa, maskRa, maskCb, maskRb = self.__interpMaskParam(
                self.fieldX, self.fieldY, inst.maskParam)

            lutx, luty = self.__createPupilGrid(lutx, luty, onepixel, maskCa,
                                                maskCb, maskRa, maskRb,
                                                self.fieldX, self.fieldY)

            # Calculate the x, y-coordinate on focal plane

            # First rotate back to reference orientation
            lutx0 = lutx * costheta + luty * sintheta
            luty0 = -lutx * sintheta + luty * costheta

            # Use the mapping at reference orientation
            lutxp0 = polyFunc(cx, lutx0, y=luty0)
            lutyp0 = polyFunc(cy, lutx0, y=luty0)

            # Rotate back to focal plane
            lutxp = lutxp0 * costheta - lutyp0 * sintheta
            lutyp = lutxp0 * sintheta + lutyp0 * costheta

            # Zemax data are in mm, therefore 1000
            sensorSamples = inst.parameter["sensorSamples"]
            pixelSize = inst.parameter["pixelSize"]
            reduced_coordi_factor = 1e-3 / (sensorSamples / 2 * pixelSize /
                                            sensorFactor)

            # Reduced coordinates, so that this can be added with the dW/dz
            lutxp = lutxp * reduced_coordi_factor
            lutyp = lutyp * reduced_coordi_factor

        else:
            print('Wrong optical model type in compensate. \n')
            return

        # Obscuration of annular aperture
        zobsR = algo.parameter["zobsR"]

        # Calculate the x, y-coordinate on focal plane
        # x' = F(x,y)*x + C*(dW/dx), y' = F(x,y)*y + C*(dW/dy)

        # In Model basis (zer: Zernike polynomials)
        if (zcCol.ndim == 1):
            lutxp = lutxp + myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR,
                                                     "dx")
            lutyp = lutyp + myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR,
                                                     "dy")

        # Make the sign to be consistent
        if (self.atype == "extra"):
            lutxp = -lutxp
            lutyp = -lutyp

        # Calculate the Jacobian matrix
        # In Model basis (zer: Zernike polynomials)
        if (zcCol.ndim == 1):
            if (model == "paraxial"):
                J = 1 + myC * ZernikeAnnularJacobian(zcCol, lutx, luty, zobsR, "1st") + \
                    myC**2 * ZernikeAnnularJacobian(zcCol, lutx, luty, zobsR, "2nd")

            elif (model == "onAxis"):
                xpox = maskScalingFactor * myA * (1 + \
                    lutx**2 * R**2. / (focalLength**2 - R**2 * lutr**2)) + \
                    myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR, "dx2")

                ypoy = maskScalingFactor * myA * (1 + \
                    luty**2 * R**2. / (focalLength**2 - R**2 * lutr**2)) + \
                    myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR, "dy2")

                xpoy = maskScalingFactor * myA * \
                    lutx * luty * R**2 / (focalLength**2 - R**2 * lutr**2) + \
                    myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR, "dxy")

                ypox = xpoy

                J = xpox * ypoy - xpoy * ypox

            elif (model == "offAxis"):
                xp0ox = polyGradFunc(cx, lutx0, luty0, "dx") * costheta - \
                        polyGradFunc(cx, lutx0, luty0, "dy") * sintheta

                yp0ox = polyGradFunc(cy, lutx0, luty0, "dx") * costheta - \
                        polyGradFunc(cy, lutx0, luty0, "dy") * sintheta

                xp0oy = polyGradFunc(cx, lutx0, luty0, "dx") * sintheta + \
                        polyGradFunc(cx, lutx0, luty0, "dy") * costheta

                yp0oy = polyGradFunc(cy, lutx0, luty0, "dx") * sintheta + \
                        polyGradFunc(cy, lutx0, luty0, "dy") * costheta

                xpox = (xp0ox*costheta - yp0ox*sintheta)*reduced_coordi_factor + \
                        myC*ZernikeAnnularGrad(zcCol, lutx, luty, zobsR, "dx2")

                ypoy = (xp0oy*sintheta + yp0oy*costheta)*reduced_coordi_factor + \
                        myC*ZernikeAnnularGrad(zcCol, lutx, luty, zobsR, "dy2")

                temp = myC * ZernikeAnnularGrad(zcCol, lutx, luty, zobsR,
                                                "dxy")

                # if temp==0,xpoy doesn't need to be symmetric about x=y
                xpoy = (xp0oy * costheta -
                        yp0oy * sintheta) * reduced_coordi_factor + temp

                # xpoy-flipud(rot90(ypox))==0 is true
                ypox = (xp0ox * sintheta +
                        yp0ox * costheta) * reduced_coordi_factor + temp

                J = xpox * ypoy - xpoy * ypox

        return lutxp, lutyp, J
Beispiel #5
0
    def testZernikeAnnularGradWrongAxis(self):

        with self.assertRaises(ValueError):
            ZernikeAnnularGrad(
                self.zerCoef, self.xx, self.yy, self.obscuration, "wrongAxis"
            )