def __init__(self, instDir, algoDir): self.inst = Instrument(instDir) self.algo = Algorithm(algoDir) self.imgIntra = CompensableImage() self.imgExtra = CompensableImage() self.opticalModel = "" self.sizeInPix = 0
def setUp(self): # Get the path of module self.modulePath = getModulePath() # Define the image folder and image names # Image data -- Don't know the final image format. # It is noted that image.readFile inuts is based on the txt file imageFolderPath = os.path.join(self.modulePath, "tests", "testData", "testImages", "LSST_NE_SN25") intra_image_name = "z11_0.25_intra.txt" extra_image_name = "z11_0.25_extra.txt" # Define fieldXY: [1.185, 1.185] or [0, 0] # This is the position of donut on the focal plane in degree fieldXY = [1.185, 1.185] # Define the optical model: "paraxial", "onAxis", "offAxis" self.opticalModel = "offAxis" # Image files Path intra_image_file = os.path.join(imageFolderPath, intra_image_name) extra_image_file = os.path.join(imageFolderPath, extra_image_name) # Theree is the difference between intra and extra images # I1: intra_focal images, I2: extra_focal Images self.I1 = CompensableImage() self.I2 = CompensableImage() self.I1.setImg(fieldXY, DefocalType.Intra, imageFile=intra_image_file) self.I2.setImg(fieldXY, DefocalType.Extra, imageFile=extra_image_file) # Set up the instrument cwfsConfigDir = os.path.join(getConfigDir(), "cwfs") instDir = os.path.join(cwfsConfigDir, "instData") self.inst = Instrument(instDir) self.inst.config(CamType.LsstCam, self.I1.getImgSizeInPix(), announcedDefocalDisInMm=1.0) # Set up the algorithm algoDir = os.path.join(cwfsConfigDir, "algo") self.algoExp = Algorithm(algoDir) self.algoExp.config("exp", self.inst) self.algoFft = Algorithm(algoDir) self.algoFft.config("fft", self.inst)
def __init__(self, instruFolderPath, algoFolderPath): """ Initialize the wavefront estimator class. Arguments: instruFolderPath {[str]} -- Path to instrument directory. algoFolderPath {[str]} -- Path to algorithm directory. """ self.algo = Algorithm(algoFolderPath) self.inst = Instrument(instruFolderPath) self.ImgIntra = CompensationImageDecorator() self.ImgExtra = CompensationImageDecorator() self.opticalModel = "" self.sizeInPix = 0
def __init__(self, instDir, algoDir): """Initialize the wavefront estimator class. Parameters ---------- instDir : str Path to instrument directory. algoDir : str Path to algorithm directory. """ self.inst = Instrument(instDir) self.algo = Algorithm(algoDir) self.imgIntra = CompensableImage() self.imgExtra = CompensableImage() self.opticalModel = "" self.sizeInPix = 0
def _runWep(self, imgIntraName, imgExtraName, offset, model): # Cut the donut image from input files centroidFindType = CentroidFindType.Otsu imgIntra = Image(centroidFindType=centroidFindType) imgExtra = Image(centroidFindType=centroidFindType) imgIntraPath = os.path.join(self.testImgDir, imgIntraName) imgExtraPath = os.path.join(self.testImgDir, imgExtraName) imgIntra.setImg(imageFile=imgIntraPath) imgExtra.setImg(imageFile=imgExtraPath) xIntra, yIntra, _ = imgIntra.getCenterAndR() imgIntraArray = imgIntra.getImg()[int(yIntra) - offset:int(yIntra) + offset, int(xIntra) - offset:int(xIntra) + offset, ] xExtra, yExtra, _ = imgExtra.getCenterAndR() imgExtraArray = imgExtra.getImg()[int(yExtra) - offset:int(yExtra) + offset, int(xExtra) - offset:int(xExtra) + offset, ] # Set the images fieldXY = (0, 0) imgCompIntra = CompensableImage(centroidFindType=centroidFindType) imgCompIntra.setImg(fieldXY, DefocalType.Intra, image=imgIntraArray) imgCompExtra = CompensableImage(centroidFindType=centroidFindType) imgCompExtra.setImg(fieldXY, DefocalType.Extra, image=imgExtraArray) # Calculate the wavefront error # Set the instrument instDir = os.path.join(getConfigDir(), "cwfs", "instData") instAuxTel = Instrument(instDir) instAuxTel.config(CamType.AuxTel, imgCompIntra.getImgSizeInPix(), announcedDefocalDisInMm=0.8) # Set the algorithm algoFolderPath = os.path.join(getConfigDir(), "cwfs", "algo") algoAuxTel = Algorithm(algoFolderPath) algoAuxTel.config("exp", instAuxTel) algoAuxTel.runIt(imgCompIntra, imgCompExtra, model) return algoAuxTel.getZer4UpInNm()
def testFFT(self): # Define the algorithm folder algoFolderPath = os.path.join(self.modulePath, "configData", "cwfs", "algo") # Define the algorithm being used: "exp" or "fft" useAlgorithm = "fft" # Define the algorithm to be used. algo = Algorithm(algoFolderPath) algo.config(useAlgorithm, self.inst, debugLevel=0) # Run it algo.runIt(self.inst, self.I1, self.I2, self.opticalModel, tol=1e-3) # Check the value Zk = algo.zer4UpNm self.assertEqual(int(Zk[7]), -192)
class WfEstimator(object): def __init__(self, instDir, algoDir): """Initialize the wavefront estimator class. Parameters ---------- instDir : str Path to instrument directory. algoDir : str Path to algorithm directory. """ self.inst = Instrument(instDir) self.algo = Algorithm(algoDir) self.imgIntra = CompensableImage() self.imgExtra = CompensableImage() self.opticalModel = "" self.sizeInPix = 0 def getAlgo(self): """Get the algorithm object. Returns ------- Algorithm Algorithm object. """ return self.algo def getInst(self): """Get the instrument object. Returns ------- Instrument Instrument object. """ return self.inst def getIntraImg(self): """Get the intra-focal donut image. Returns ------- CompensableImage Intra-focal donut image. """ return self.imgIntra def getExtraImg(self): """Get the extra-focal donut image. Returns ------- CompensableImage Extra-focal donut image. """ return self.imgExtra def getOptModel(self): """Get the optical model. Returns ------- str Optical model. """ return self.opticalModel def getSizeInPix(self): """Get the donut image size in pixel defined by the config() function. Returns ------- int Donut image size in pixel """ return self.sizeInPix def reset(self): """ Reset the calculation for the new input images with the same algorithm settings. """ self.algo.reset() def config( self, solver="exp", camType=CamType.LsstCam, opticalModel="offAxis", defocalDisInMm=1.5, sizeInPix=120, centroidFindType=CentroidFindType.RandomWalk, debugLevel=0, ): """Configure the TIE solver. Parameters ---------- solver : str, optional Algorithm to solve the Poisson's equation in the transport of intensity equation (TIE). It can be "fft" or "exp" here. (the default is "exp".) camType : enum 'CamType', optional Camera type. (the default is CamType.LsstCam.) opticalModel : str, optional Optical model. It can be "paraxial", "onAxis", or "offAxis". (the default is "offAxis".) defocalDisInMm : float, optional Defocal distance in mm. (the default is 1.5.) sizeInPix : int, optional Wavefront image pixel size. (the default is 120.) centroidFindType : enum 'CentroidFindType', optional Algorithm to find the centroid of donut. (the default is CentroidFindType.RandomWalk.) debugLevel : int, optional Show the information under the running. If the value is higher, the information shows more. It can be 0, 1, 2, or 3. (the default is 0.) Raises ------ ValueError Wrong Poisson solver name. ValueError Wrong optical model. """ if solver not in ("exp", "fft"): raise ValueError("Poisson solver can not be '%s'." % solver) if opticalModel not in ("paraxial", "onAxis", "offAxis"): raise ValueError("Optical model can not be '%s'." % opticalModel) else: self.opticalModel = opticalModel # Update the instrument name self.sizeInPix = int(sizeInPix) self.inst.config(camType, self.sizeInPix, announcedDefocalDisInMm=defocalDisInMm) self.algo.config(solver, self.inst, debugLevel=debugLevel) # Reset the centroid find algorithm if not the default one if centroidFindType != CentroidFindType.RandomWalk: self.imgIntra = CompensableImage(centroidFindType=centroidFindType) self.imgExtra = CompensableImage(centroidFindType=centroidFindType) def setImg(self, fieldXY, defocalType, image=None, imageFile=None): """Set the wavefront image. Parameters ---------- fieldXY : tuple or list Position of donut on the focal plane in degree for intra- and extra-focal images. defocalType : enum 'DefocalType' Defocal type of image. image : numpy.ndarray, optional Array of image. (the default is None.) imageFile : str, optional Path of image file. (the default is None.) """ if defocalType == DefocalType.Intra: img = self.imgIntra elif defocalType == DefocalType.Extra: img = self.imgExtra img.setImg(fieldXY, defocalType, image=image, imageFile=imageFile) def calWfsErr(self, tol=1e-3, showZer=False, showPlot=False): """Calculate the wavefront error. Parameters ---------- tol : float, optional [description] (the default is 1e-3.) showZer : bool, optional Decide to show the annular Zernike polynomails or not. (the default is False.) showPlot : bool, optional Decide to show the plot or not. (the default is False.) Returns ------- numpy.ndarray Coefficients of Zernike polynomials (z4 - z22). Raises ------ RuntimeError Input image shape is wrong. """ # Check the image size for img in (self.imgIntra, self.imgExtra): d1, d2 = img.getImg().shape if (d1 != self.sizeInPix) or (d2 != self.sizeInPix): raise RuntimeError( "Input image shape is (%d, %d), not required (%d, %d)" % (d1, d2, self.sizeInPix, self.sizeInPix)) # Calculate the wavefront error. # Run cwfs self.algo.runIt(self.imgIntra, self.imgExtra, self.opticalModel, tol=tol) # Show the Zernikes Zn (n>=4) if showZer: self.algo.outZer4Up(showPlot=showPlot) return self.algo.getZer4UpInNm()
def _runWEP( self, instDir, algoFolderPath, useAlgorithm, imageFolderPath, intra_image_name, extra_image_name, fieldXY, opticalModel, showFig=False, ): # Image files Path intra_image_file = os.path.join(imageFolderPath, intra_image_name) extra_image_file = os.path.join(imageFolderPath, extra_image_name) # There is the difference between intra and extra images # I1: intra_focal images, I2: extra_focal Images I1 = CompensableImage() I2 = CompensableImage() I1.setImg(fieldXY, DefocalType.Intra, imageFile=intra_image_file) I2.setImg(fieldXY, DefocalType.Extra, imageFile=extra_image_file) # Set the instrument inst = Instrument(instDir) inst.config(CamType.LsstCam, I1.getImgSizeInPix(), announcedDefocalDisInMm=1.0) # Define the algorithm to be used. algo = Algorithm(algoFolderPath) algo.config(useAlgorithm, inst, debugLevel=0) # Plot the original wavefront images if showFig: plotImage(I1.image, title="intra image") plotImage(I2.image, title="extra image") # Run it algo.runIt(I1, I2, opticalModel, tol=1e-3) # Show the Zernikes Zn (n>=4) algo.outZer4Up(showPlot=False) # Plot the final conservated images and wavefront if showFig: plotImage(I1.image, title="Compensated intra image") plotImage(I2.image, title="Compensated extra image") # Plot the Wavefront plotImage(algo.wcomp, title="Final wavefront") plotImage( algo.wcomp, title="Final wavefront with pupil mask applied", mask=algo.pMask, ) # Return the Zernikes Zn (n>=4) return algo.getZer4UpInNm()
class TestAlgorithm(unittest.TestCase): """Test the Algorithm class.""" def setUp(self): # Get the path of module self.modulePath = getModulePath() # Define the image folder and image names # Image data -- Don't know the final image format. # It is noted that image.readFile inuts is based on the txt file imageFolderPath = os.path.join(self.modulePath, "tests", "testData", "testImages", "LSST_NE_SN25") intra_image_name = "z11_0.25_intra.txt" extra_image_name = "z11_0.25_extra.txt" # Define fieldXY: [1.185, 1.185] or [0, 0] # This is the position of donut on the focal plane in degree fieldXY = [1.185, 1.185] # Define the optical model: "paraxial", "onAxis", "offAxis" self.opticalModel = "offAxis" # Image files Path intra_image_file = os.path.join(imageFolderPath, intra_image_name) extra_image_file = os.path.join(imageFolderPath, extra_image_name) # Theree is the difference between intra and extra images # I1: intra_focal images, I2: extra_focal Images self.I1 = CompensableImage() self.I2 = CompensableImage() self.I1.setImg(fieldXY, DefocalType.Intra, imageFile=intra_image_file) self.I2.setImg(fieldXY, DefocalType.Extra, imageFile=extra_image_file) # Set up the instrument cwfsConfigDir = os.path.join(getConfigDir(), "cwfs") instDir = os.path.join(cwfsConfigDir, "instData") self.inst = Instrument(instDir) self.inst.config(CamType.LsstCam, self.I1.getImgSizeInPix(), announcedDefocalDisInMm=1.0) # Set up the algorithm algoDir = os.path.join(cwfsConfigDir, "algo") self.algoExp = Algorithm(algoDir) self.algoExp.config("exp", self.inst) self.algoFft = Algorithm(algoDir) self.algoFft.config("fft", self.inst) def testGetDebugLevel(self): self.assertEqual(self.algoExp.getDebugLevel(), 0) def testSetDebugLevel(self): self.algoExp.config("exp", self.inst, debugLevel=3) self.assertEqual(self.algoExp.getDebugLevel(), 3) self.algoExp.setDebugLevel(0) self.assertEqual(self.algoExp.getDebugLevel(), 0) def testGetZer4UpInNm(self): zer4UpNm = self.algoExp.getZer4UpInNm() self.assertTrue(isinstance(zer4UpNm, np.ndarray)) def testGetPoissonSolverName(self): self.assertEqual(self.algoExp.getPoissonSolverName(), "exp") self.assertEqual(self.algoFft.getPoissonSolverName(), "fft") def testGetNumOfZernikes(self): self.assertEqual(self.algoExp.getNumOfZernikes(), 22) self.assertEqual(self.algoFft.getNumOfZernikes(), 22) def testGetZernikeTerms(self): zTerms = self.algoExp.getZernikeTerms() self.assertTrue(type(zTerms[0]), int) self.assertEqual(len(zTerms), self.algoExp.getNumOfZernikes()) self.assertEqual(zTerms[1], 1) self.assertEqual(zTerms[-1], self.algoExp.getNumOfZernikes() - 1) zTerms = self.algoFft.getZernikeTerms() self.assertTrue(type(zTerms[0]), int) self.assertEqual(len(zTerms), self.algoExp.getNumOfZernikes()) def testGetObsOfZernikes(self): self.assertEqual(self.algoExp.getObsOfZernikes(), self.inst.getObscuration()) self.assertEqual(self.algoFft.getObsOfZernikes(), self.inst.getObscuration()) def testGetNumOfOuterItr(self): self.assertEqual(self.algoExp.getNumOfOuterItr(), 14) self.assertEqual(self.algoFft.getNumOfOuterItr(), 14) def testGetNumOfInnerItr(self): self.assertEqual(self.algoFft.getNumOfInnerItr(), 6) def testGetFeedbackGain(self): self.assertEqual(self.algoExp.getFeedbackGain(), 0.6) self.assertEqual(self.algoFft.getFeedbackGain(), 0.6) def testGetOffAxisPolyOrder(self): self.assertEqual(self.algoExp.getOffAxisPolyOrder(), 10) self.assertEqual(self.algoFft.getOffAxisPolyOrder(), 10) def testGetCompensatorMode(self): self.assertEqual(self.algoExp.getCompensatorMode(), "zer") self.assertEqual(self.algoFft.getCompensatorMode(), "zer") def testGetCompSequence(self): compSequence = self.algoExp.getCompSequence() self.assertTrue(isinstance(compSequence, np.ndarray)) self.assertEqual(compSequence.dtype, int) self.assertEqual(len(compSequence), self.algoExp.getNumOfOuterItr()) self.assertEqual(compSequence[0], 4) self.assertEqual(compSequence[-1], 22) compSequence = self.algoFft.getCompSequence() self.assertEqual(len(compSequence), self.algoFft.getNumOfOuterItr()) def testGetBoundaryThickness(self): self.assertEqual(self.algoExp.getBoundaryThickness(), 8) self.assertEqual(self.algoFft.getBoundaryThickness(), 1) def testGetFftDimension(self): self.assertEqual(self.algoFft.getFftDimension(), 128) def testGetSignalClipSequence(self): sumclipSequence = self.algoFft.getSignalClipSequence() self.assertTrue(isinstance(sumclipSequence, np.ndarray)) self.assertEqual(len(sumclipSequence), self.algoExp.getNumOfOuterItr() + 1) self.assertEqual(sumclipSequence[0], 0.33) self.assertEqual(sumclipSequence[-1], 0.51) def testGetMaskScalingFactor(self): self.assertAlmostEqual(self.algoExp.getMaskScalingFactor(), 1.0939, places=4) self.assertAlmostEqual(self.algoFft.getMaskScalingFactor(), 1.0939, places=4) def testGetWavefrontMapEstiInIter0(self): self.assertRaises(RuntimeError, self.algoExp.getWavefrontMapEsti) def testGetWavefrontMapEstiAndResidual(self): self.algoExp.runIt(self.I1, self.I2, self.opticalModel, tol=1e-3) wavefrontMapEsti = self.algoExp.getWavefrontMapEsti() wavefrontMapEsti[np.isnan(wavefrontMapEsti)] = 0 self.assertGreater(np.sum(np.abs(wavefrontMapEsti)), 4.8e-4) wavefrontMapResidual = self.algoExp.getWavefrontMapResidual() wavefrontMapResidual[np.isnan(wavefrontMapResidual)] = 0 self.assertLess(np.sum(np.abs(wavefrontMapResidual)), 2.5e-6) def testItr0(self): self.algoExp.itr0(self.I1, self.I2, self.opticalModel) zer4UpNm = self.algoExp.getZer4UpInNm() self.assertEqual( np.sum(np.abs(np.rint(zer4UpNm) - self._getAnsItr0())), 0) def _getAnsItr0(self): return [ 31, -69, -21, 84, 44, -53, 48, -146, 6, 10, 13, -5, 1, -12, -8, 7, 0, -6, 11, ] def testNextItrWithOneIter(self): self.algoExp.nextItr(self.I1, self.I2, self.opticalModel, nItr=1) zer4UpNm = self.algoExp.getZer4UpInNm() self.assertEqual( np.sum(np.abs(np.rint(zer4UpNm) - self._getAnsItr0())), 0) def testNextItrWithTwoIter(self): self.algoExp.nextItr(self.I1, self.I2, self.opticalModel, nItr=2) zer4UpNm = self.algoExp.getZer4UpInNm() ansRint = [ 40, -80, -18, 92, 44.0, -52, 54, -146, 5, 10, 15, -3, -0, -12, -8, 7, 1, -3, 12, ] self.assertEqual(np.sum(np.abs(np.rint(zer4UpNm) - ansRint)), 0) def testIter0AndNextIterToCheckReset(self): self.algoExp.itr0(self.I1, self.I2, self.opticalModel) tmp1 = self.algoExp.getZer4UpInNm() self.algoExp.nextItr(self.I1, self.I2, self.opticalModel, nItr=2) # itr0() should reset the images and ignore the effect from nextItr() self.algoExp.itr0(self.I1, self.I2, self.opticalModel) tmp2 = self.algoExp.getZer4UpInNm() difference = np.sum(np.abs(tmp1 - tmp2)) self.assertEqual(difference, 0) def testRunItOfExp(self): self.algoExp.runIt(self.I1, self.I2, self.opticalModel, tol=1e-3) # Check the value zk = self.algoExp.getZer4UpInNm() self.assertEqual(int(zk[7]), -192) def testResetAfterFullCalc(self): self.algoExp.runIt(self.I1, self.I2, self.opticalModel, tol=1e-3) # Reset and check the calculation again fieldXY = [self.I1.fieldX, self.I1.fieldY] self.I1.setImg(fieldXY, self.I1.getDefocalType(), image=self.I1.getImgInit()) self.I2.setImg(fieldXY, self.I2.getDefocalType(), image=self.I2.getImgInit()) self.algoExp.reset() self.algoExp.runIt(self.I1, self.I2, self.opticalModel, tol=1e-3) zk = self.algoExp.getZer4UpInNm() self.assertEqual(int(zk[7]), -192) def testRunItOfFft(self): self.algoFft.runIt(self.I1, self.I2, self.opticalModel, tol=1e-3) zk = self.algoFft.getZer4UpInNm() self.assertEqual(int(zk[7]), -192)
def calcWfErr( self, centroidFindType, fieldXY, camType, algoName, announcedDefocalDisInMm, opticalModel, imageIntra=None, imageExtra=None, imageFileIntra=None, imageFileExtra=None, ): """Calculate the wavefront error. Parameters ---------- centroidFindType : enum 'CentroidFindType' Algorithm to find the centroid of donut. fieldXY : tuple or list Position of donut on the focal plane in degree (field x, field y). camType : enum 'CamType' Camera type. algoName : str Algorithm configuration file to solve the Poisson's equation in the transport of intensity equation (TIE). It can be "fft" or "exp" here. announcedDefocalDisInMm : float Announced defocal distance in mm. It is noted that the defocal distance offset used in calculation might be different from this value. opticalModel : str Optical model. It can be "paraxial", "onAxis", or "offAxis". imageIntra : numpy.ndarray, optional Array of intra-focal image. (the default is None.) imageExtra : numpy.ndarray, optional Array of extra-focal image. (the default is None.) imageFileIntra : str, optional Path of intra-focal image file. (the default is None.) imageFileExtra : str, optional Path of extra-focal image file. (the default is None.) Returns ------- numpy.ndarray Zernike polynomials of z4-zn in nm. """ # Set the defocal images imgIntra = CompensableImage(centroidFindType=centroidFindType) imgExtra = CompensableImage(centroidFindType=centroidFindType) imgIntra.setImg( fieldXY, DefocalType.Intra, image=imageIntra, imageFile=imageFileIntra ) imgExtra.setImg( fieldXY, DefocalType.Extra, image=imageExtra, imageFile=imageFileExtra ) # Set the instrument instDir = os.path.join(getConfigDir(), "cwfs", "instData") inst = Instrument(instDir) inst.config( camType, imgIntra.getImgSizeInPix(), announcedDefocalDisInMm=announcedDefocalDisInMm, ) # Define the algorithm to be used. algoFolderPath = os.path.join(getConfigDir(), "cwfs", "algo") algo = Algorithm(algoFolderPath) algo.config(algoName, inst, debugLevel=0) # Run it algo.runIt(imgIntra, imgExtra, opticalModel, tol=1e-3) # Return the Zernikes Zn (n>=4) return algo.getZer4UpInNm()
class WfEstimator(object): def __init__(self, instruFolderPath, algoFolderPath): """ Initialize the wavefront estimator class. Arguments: instruFolderPath {[str]} -- Path to instrument directory. algoFolderPath {[str]} -- Path to algorithm directory. """ self.algo = Algorithm(algoFolderPath) self.inst = Instrument(instruFolderPath) self.ImgIntra = CompensationImageDecorator() self.ImgExtra = CompensationImageDecorator() self.opticalModel = "" self.sizeInPix = 0 def getAlgo(self): """Get the algorithm object. Returns ------- Algorithm Algorithm object. """ return self.algo def getInst(self): """Get the instrument object. Returns ------- Instrument Instrument object. """ return self.inst def getIntraImg(self): """Get the intra-focal donut image. Returns ------- CompensationImageDecorator Intra-focal donut image. """ return self.ImgIntra def getExtraImg(self): """Get the extra-focal donut image. Returns ------- CompensationImageDecorator Extra-focal donut image. """ return self.ImgExtra def getOptModel(self): """Get the optical model. Returns ------- str Optical model. """ return self.opticalModel def getSizeInPix(self): """Get the donut image size in pixel defined by the config() function. Returns ------- int Donut image size in pixel """ return self.sizeInPix def reset(self): """ Reset the calculation for the new input images with the same algorithm settings. """ self.algo.reset() def config(self, solver="exp", instName="lsst", opticalModel="offAxis", defocalDisInMm=None, sizeInPix=120, debugLevel=0): """ Configure the TIE solver. Keyword Arguments: solver {[str]} -- Algorithm to solve the Poisson's equation in the transport of intensity equation (TIE). It can be "fft" or "exp" here. (default: {"exp"}) instName {[str]} -- Instrument name. It is "lsst" in the baseline. (default: {"lsst"}) opticalModel {[str]} -- Optical model. It can be "paraxial", "onAxis", or "offAxis". (default: {"offAxis"}) defocalDisInMm {[float]} -- Defocal distance in mm. (default: {None}) sizeInPix {[int]} -- Wavefront image pixel size. (default: {120}) debugLevel {[int]} -- Show the information under the running. If the value is higher, the information shows more. It can be 0, 1, 2, or 3. (default: {0}) Raises: ValueError -- Wrong instrument name. ValueError -- No intra-focal image. ValueError -- Wrong Poisson solver name. ValueError -- Wrong optical model. """ # Check the inputs and assign the parameters used in the TIE # Need to change the way to hold the instance of Instrument and Algorithm # Update the isnstrument name if (defocalDisInMm is not None): instName = instName + str(int(10 * defocalDisInMm)) if instName not in ("lsst", "lsst05", "lsst10", "lsst15", "lsst20", "lsst25", "comcam10", "comcam15", "comcam20"): raise ValueError("Instrument can not be '%s'." % instName) # Set the available wavefront image size (n x n) self.sizeInPix = int(sizeInPix) # Configurate the instrument self.inst.config(instName, self.sizeInPix) if solver not in ("exp", "fft"): raise ValueError("Poisson solver can not be '%s'." % solver) else: self.algo.config(solver, self.inst, debugLevel=debugLevel) if opticalModel not in ("paraxial", "onAxis", "offAxis"): raise ValueError("Optical model can not be '%s'." % opticalModel) else: self.opticalModel = opticalModel def setImg(self, fieldXY, image=None, imageFile=None, defocalType=None): """ Set the wavefront image. Arguments: fieldXY {[float]} -- Position of donut on the focal plane in degree for intra- and extra-focal images. Keyword Arguments: image {[float]} -- Array of image. (default: {None}) imageFile {[str]} -- Path of image file. (default: {None}) defocalType {[str]} -- Type of image. It should be "intra" or "extra". (default: {None}) Raises: ValueError -- Wrong defocal type. """ # Check the defocal type if defocalType not in (self.ImgIntra.INTRA, self.ImgIntra.EXTRA): raise ValueError("Defocal type can not be '%s'." % defocalType) # Read the image and assign the type if (defocalType == self.ImgIntra.INTRA): self.ImgIntra.setImg(fieldXY, image=image, imageFile=imageFile, atype=defocalType) elif (defocalType == self.ImgIntra.EXTRA): self.ImgExtra.setImg(fieldXY, image=image, imageFile=imageFile, atype=defocalType) def calWfsErr(self, tol=1e-3, showZer=False, showPlot=False): """ Calculate the wavefront error. Keyword Arguments: tol {number} -- Tolerance of difference of coefficients of Zk polynomials compared with the previours iteration. (default: {1e-3}) showZer {bool} -- Decide to show the annular Zernike polynomails or not. (default: {False}) showPlot {bool} -- Decide to show the plot or not. (default: {False}) Returns: [float] -- Coefficients of Zernike polynomials (z4 - z22). """ # Check the image size for img in (self.ImgIntra, self.ImgExtra): d1, d2 = img.image.shape if (d1 != self.sizeInPix) or (d2 != self.sizeInPix): raise RuntimeError( "Input image shape is (%d, %d), not required (%d, %d)" % (d1, d2, self.sizeInPix, self.sizeInPix)) # Calculate the wavefront error. # Run cwfs self.algo.runIt(self.inst, self.ImgIntra, self.ImgExtra, self.opticalModel, tol=tol) # Show the Zernikes Zn (n>=4) if (showZer): self.algo.outZer4Up(showPlot=showPlot) return self.algo.zer4UpNm def outParam(self, filename=None): """ Put the information of images, instrument, and algorithm on terminal or file. Keyword Arguments: filename {[str]} -- Name of output file. (default: {None}) """ # Write the parameters into a file if needed. if (filename is not None): fout = open(filename, "w") else: fout = sys.stdout # Write the information of image and optical model if (self.ImgIntra.name is not None): fout.write("Intra image: \t %s\n" % self.ImgIntra.name) if (self.ImgIntra.fieldX is not None): fout.write("Intra image field in deg =(%6.3f, %6.3f)\n" % (self.ImgIntra.fieldX, self.ImgIntra.fieldY)) if (self.ImgExtra.name is not None): fout.write("Extra image: \t %s\n" % self.ImgExtra.name) if (self.ImgExtra.fieldX is not None): fout.write("Extra image field in deg =(%6.3f, %6.3f)\n" % (self.ImgExtra.fieldX, self.ImgExtra.fieldY)) if (self.opticalModel is not None): fout.write("Using optical model:\t %s\n" % self.opticalModel) # Read the instrument file if (self.inst.filename is not None): self.__readConfigFile(fout, self.inst, "instrument") # Read the algorithm file if (self.algo.filename is not None): self.__readConfigFile(fout, self.algo, "algorithm") # Close the file if (filename is not None): fout.close() def __readConfigFile(self, fout, config, configName): """ Read the configuration file Arguments: fout {[file]} -- File instance. config {[metadata]} -- Instance of configuration. It is Instrument or Algorithm here. configName {[str]} -- Name of configuration. """ # Create a new line fout.write("\n") # Open the file fconfig = open(config.filename) fout.write("---" + configName + " file: --- %s ----------\n" % config.filename) # Read the file information iscomment = False for line in fconfig: line = line.strip() if (line.startswith("###")): iscomment = ~iscomment if (not (line.startswith("#")) and (not iscomment) and len(line) > 0): fout.write(line + "\n") # Close the file fconfig.close()
class WfEstimator(object): def __init__(self, instDir, algoDir): """Initialize the wavefront estimator class. Parameters ---------- instDir : str Path to instrument directory. algoDir : str Path to algorithm directory. """ self.inst = Instrument(instDir) self.algo = Algorithm(algoDir) self.imgIntra = CompensableImage() self.imgExtra = CompensableImage() self.opticalModel = "" self.sizeInPix = 0 def getAlgo(self): """Get the algorithm object. Returns ------- Algorithm Algorithm object. """ return self.algo def getInst(self): """Get the instrument object. Returns ------- Instrument Instrument object. """ return self.inst def getIntraImg(self): """Get the intra-focal donut image. Returns ------- CompensableImage Intra-focal donut image. """ return self.imgIntra def getExtraImg(self): """Get the extra-focal donut image. Returns ------- CompensableImage Extra-focal donut image. """ return self.imgExtra def getOptModel(self): """Get the optical model. Returns ------- str Optical model. """ return self.opticalModel def getSizeInPix(self): """Get the donut image size in pixel defined by the config() function. Returns ------- int Donut image size in pixel """ return self.sizeInPix def reset(self): """ Reset the calculation for the new input images with the same algorithm settings. """ self.algo.reset() def config(self, solver="exp", camType=CamType.LsstCam, opticalModel="offAxis", defocalDisInMm=None, sizeInPix=120, debugLevel=0): """Configure the TIE solver. Parameters ---------- solver : str, optional Algorithm to solve the Poisson's equation in the transport of intensity equation (TIE). It can be "fft" or "exp" here. (the default is "exp".) camType : enum 'CamType' Camera type. (the default is CamType.LsstCam.) opticalModel : str, optional Optical model. It can be "paraxial", "onAxis", or "offAxis". (the default is "offAxis".) defocalDisInMm : float, optional Defocal distance in mm. (the default is None.) sizeInPix : int, optional Wavefront image pixel size. (the default is 120.) debugLevel : int, optional Show the information under the running. If the value is higher, the information shows more. It can be 0, 1, 2, or 3. (the default is 0.) Raises ------ ValueError Wrong Poisson solver name. ValueError Wrong optical model. """ if solver not in ("exp", "fft"): raise ValueError("Poisson solver can not be '%s'." % solver) if opticalModel not in ("paraxial", "onAxis", "offAxis"): raise ValueError("Optical model can not be '%s'." % opticalModel) else: self.opticalModel = opticalModel # Update the isnstrument name if (defocalDisInMm is None): defocalDisInMm = 1.5 self.sizeInPix = int(sizeInPix) self.inst.config(camType, self.sizeInPix, announcedDefocalDisInMm=defocalDisInMm) self.algo.config(solver, self.inst, debugLevel=debugLevel) def setImg(self, fieldXY, defocalType, image=None, imageFile=None): """Set the wavefront image. Parameters ---------- fieldXY : tuple or list Position of donut on the focal plane in degree for intra- and extra-focal images. defocalType : enum 'DefocalType' Defocal type of image. image : numpy.ndarray, optional Array of image. (the default is None.) imageFile : str, optional Path of image file. (the default is None.) """ if (defocalType == DefocalType.Intra): img = self.imgIntra elif (defocalType == DefocalType.Extra): img = self.imgExtra img.setImg(fieldXY, defocalType, image=image, imageFile=imageFile) def calWfsErr(self, tol=1e-3, showZer=False, showPlot=False): """Calculate the wavefront error. Parameters ---------- tol : float, optional [description] (the default is 1e-3.) showZer : bool, optional Decide to show the annular Zernike polynomails or not. (the default is False.) showPlot : bool, optional Decide to show the plot or not. (the default is False.) Returns ------- numpy.ndarray Coefficients of Zernike polynomials (z4 - z22). Raises ------ RuntimeError Input image shape is wrong. """ # Check the image size for img in (self.imgIntra, self.imgExtra): d1, d2 = img.getImg().shape if (d1 != self.sizeInPix) or (d2 != self.sizeInPix): raise RuntimeError("Input image shape is (%d, %d), not required (%d, %d)" % ( d1, d2, self.sizeInPix, self.sizeInPix)) # Calculate the wavefront error. # Run cwfs self.algo.runIt(self.imgIntra, self.imgExtra, self.opticalModel, tol=tol) # Show the Zernikes Zn (n>=4) if (showZer): self.algo.outZer4Up(showPlot=showPlot) return self.algo.getZer4UpInNm()
def testExp(self): # Define the algorithm folder algoFolderPath = os.path.join(self.modulePath, "configData", "cwfs", "algo") # Define the algorithm being used: "exp" or "fft" useAlgorithm = "exp" # Define the algorithm to be used. algo = Algorithm(algoFolderPath) algo.config(useAlgorithm, self.inst, debugLevel=3) algo.setDebugLevel(0) self.assertEqual(algo.debugLevel, 0) # Test functions: itr0() and nextItr() algo.itr0(self.inst, self.I1, self.I2, self.opticalModel) tmp1 = algo.zer4UpNm algo.nextItr(self.inst, self.I1, self.I2, self.opticalModel, nItr=2) algo.itr0(self.inst, self.I1, self.I2, self.opticalModel) tmp2 = algo.zer4UpNm difference = np.sum(np.abs(tmp1 - tmp2)) self.assertEqual(difference, 0) # Run it algo.runIt(self.inst, self.I1, self.I2, self.opticalModel, tol=1e-3) # Check the value Zk = algo.zer4UpNm self.assertEqual(int(Zk[7]), -192) # Reset and check the calculation again fieldXY = [self.I1.fieldX, self.I1.fieldY] self.I1.setImg(fieldXY, image=self.I1.image0, atype=self.I1.atype) self.I2.setImg(fieldXY, image=self.I2.image0, atype=self.I2.atype) algo.reset() algo.runIt(self.inst, self.I1, self.I2, self.opticalModel, tol=1e-3) Zk = algo.zer4UpNm self.assertEqual(int(Zk[7]), -192)
def runWEP(instDir, algoFolderPath, useAlgorithm, imageFolderPath, intra_image_name, extra_image_name, fieldXY, opticalModel, showFig=False): """Calculate the coefficients of normal/ annular Zernike polynomials based on the provided instrument, algorithm, and optical model. Parameters ---------- instDir : str Path to instrument folder. algoFolderPath : str Path to algorithm folder. useAlgorithm : str Algorithm to solve the Poisson's equation in the transport of intensity equation (TIE). It can be "fft" or "exp" here. imageFolderPath : str Path to image folder. intra_image_name : str File name of intra-focal image. extra_image_name : str File name of extra-focal image. fieldXY : tuple Position of donut on the focal plane in degree for intra- and extra-focal images. opticalModel : str Optical model. It can be "paraxial", "onAxis", or "offAxis". showFig : bool, optional Show the wavefront image and compenstated image or not. (the default is False.) Returns ------- numpy.ndarray Coefficients of Zernike polynomials (z4 - z22). """ # Image files Path intra_image_file = os.path.join(imageFolderPath, intra_image_name) extra_image_file = os.path.join(imageFolderPath, extra_image_name) # There is the difference between intra and extra images # I1: intra_focal images, I2: extra_focal Images I1 = CompensableImage() I2 = CompensableImage() I1.setImg(fieldXY, DefocalType.Intra, imageFile=intra_image_file) I2.setImg(fieldXY, DefocalType.Extra, imageFile=extra_image_file) # Set the instrument inst = Instrument(instDir) inst.config(CamType.LsstCam, I1.getImgSizeInPix(), announcedDefocalDisInMm=1.0) # Define the algorithm to be used. algo = Algorithm(algoFolderPath) algo.config(useAlgorithm, inst, debugLevel=0) # Plot the original wavefront images if (showFig): plotImage(I1.image, title="intra image") plotImage(I2.image, title="extra image") # Run it algo.runIt(I1, I2, opticalModel, tol=1e-3) # Show the Zernikes Zn (n>=4) algo.outZer4Up(showPlot=False) # Plot the final conservated images and wavefront if (showFig): plotImage(I1.image, title="Compensated intra image") plotImage(I2.image, title="Compensated extra image") # Plot the Wavefront plotImage(algo.wcomp, title="Final wavefront") plotImage(algo.wcomp, title="Final wavefront with pupil mask applied", mask=algo.pMask) # Return the Zernikes Zn (n>=4) return algo.getZer4UpInNm()
class TestAlgorithm(unittest.TestCase): """Test the Algorithm class.""" def setUp(self): # Get the path of module self.modulePath = getModulePath() # Define the image folder and image names # Image data -- Don't know the final image format. # It is noted that image.readFile inuts is based on the txt file imageFolderPath = os.path.join(self.modulePath, "tests", "testData", "testImages", "LSST_NE_SN25") intra_image_name = "z11_0.25_intra.txt" extra_image_name = "z11_0.25_extra.txt" # Define fieldXY: [1.185, 1.185] or [0, 0] # This is the position of donut on the focal plane in degree fieldXY = [1.185, 1.185] # Define the optical model: "paraxial", "onAxis", "offAxis" self.opticalModel = "offAxis" # Image files Path intra_image_file = os.path.join(imageFolderPath, intra_image_name) extra_image_file = os.path.join(imageFolderPath, extra_image_name) # Theree is the difference between intra and extra images # I1: intra_focal images, I2: extra_focal Images self.I1 = CompensableImage() self.I2 = CompensableImage() self.I1.setImg(fieldXY, DefocalType.Intra, imageFile=intra_image_file) self.I2.setImg(fieldXY, DefocalType.Extra, imageFile=extra_image_file) # Set up the instrument cwfsConfigDir = os.path.join(getConfigDir(), "cwfs") instDir = os.path.join(cwfsConfigDir, "instData") self.inst = Instrument(instDir) self.inst.config(CamType.LsstCam, self.I1.getImgSizeInPix(), announcedDefocalDisInMm=1.0) # Set up the algorithm algoDir = os.path.join(cwfsConfigDir, "algo") self.algoExp = Algorithm(algoDir) self.algoExp.config("exp", self.inst) self.algoFft = Algorithm(algoDir) self.algoFft.config("fft", self.inst) def testGetDebugLevel(self): self.assertEqual(self.algoExp.getDebugLevel(), 0) def testSetDebugLevel(self): self.algoExp.config("exp", self.inst, debugLevel=3) self.assertEqual(self.algoExp.getDebugLevel(), 3) self.algoExp.setDebugLevel(0) self.assertEqual(self.algoExp.getDebugLevel(), 0) def testGetZer4UpInNm(self): zer4UpNm = self.algoExp.getZer4UpInNm() self.assertTrue(isinstance(zer4UpNm, np.ndarray)) def testGetPoissonSolverName(self): self.assertEqual(self.algoExp.getPoissonSolverName(), "exp") self.assertEqual(self.algoFft.getPoissonSolverName(), "fft") def testGetNumOfZernikes(self): self.assertEqual(self.algoExp.getNumOfZernikes(), 22) self.assertEqual(self.algoFft.getNumOfZernikes(), 22) def testGetZernikeTerms(self): zTerms = self.algoExp.getZernikeTerms() self.assertTrue(zTerms.dtype, int) self.assertEqual(len(zTerms), self.algoExp.getNumOfZernikes()) self.assertEqual(zTerms[0], 1) self.assertEqual(zTerms[-1], self.algoExp.getNumOfZernikes()) zTerms = self.algoFft.getZernikeTerms() self.assertTrue(zTerms.dtype, int) self.assertEqual(len(zTerms), self.algoExp.getNumOfZernikes()) def testGetObsOfZernikes(self): self.assertEqual(self.algoExp.getObsOfZernikes(), self.inst.getObscuration()) self.assertEqual(self.algoFft.getObsOfZernikes(), self.inst.getObscuration()) def testGetNumOfOuterItr(self): self.assertEqual(self.algoExp.getNumOfOuterItr(), 14) self.assertEqual(self.algoFft.getNumOfOuterItr(), 14) def testGetNumOfInnerItr(self): self.assertEqual(self.algoFft.getNumOfInnerItr(), 6) def testGetFeedbackGain(self): self.assertEqual(self.algoExp.getFeedbackGain(), 0.6) self.assertEqual(self.algoFft.getFeedbackGain(), 0.6) def testGetOffAxisPolyOrder(self): self.assertEqual(self.algoExp.getOffAxisPolyOrder(), 10) self.assertEqual(self.algoFft.getOffAxisPolyOrder(), 10) def testGetCompensatorMode(self): self.assertEqual(self.algoExp.getCompensatorMode(), "zer") self.assertEqual(self.algoFft.getCompensatorMode(), "zer") def testGetCompSequence(self): compSequence = self.algoExp.getCompSequence() self.assertTrue(isinstance(compSequence, np.ndarray)) self.assertEqual(compSequence.dtype, int) self.assertEqual(len(compSequence), self.algoExp.getNumOfOuterItr()) self.assertEqual(compSequence[0], 4) self.assertEqual(compSequence[-1], 22) compSequence = self.algoFft.getCompSequence() self.assertEqual(len(compSequence), self.algoFft.getNumOfOuterItr()) def testGetBoundaryThickness(self): self.assertEqual(self.algoExp.getBoundaryThickness(), 8) self.assertEqual(self.algoFft.getBoundaryThickness(), 1) def testGetFftDimension(self): self.assertEqual(self.algoFft.getFftDimension(), 128) def testGetSignalClipSequence(self): sumclipSequence = self.algoFft.getSignalClipSequence() self.assertTrue(isinstance(sumclipSequence, np.ndarray)) self.assertEqual(len(sumclipSequence), self.algoExp.getNumOfOuterItr()+1) self.assertEqual(sumclipSequence[0], 0.33) self.assertEqual(sumclipSequence[-1], 0.51) def testGetMaskScalingFactor(self): self.assertAlmostEqual(self.algoExp.getMaskScalingFactor(), 1.0939, places=4) self.assertAlmostEqual(self.algoFft.getMaskScalingFactor(), 1.0939, places=4) def testRunItOfExp(self): # Test functions: itr0() and nextItr() self.algoExp.itr0(self.I1, self.I2, self.opticalModel) tmp1 = self.algoExp.getZer4UpInNm() self.algoExp.nextItr(self.I1, self.I2, self.opticalModel, nItr=2) self.algoExp.itr0(self.I1, self.I2, self.opticalModel) tmp2 = self.algoExp.getZer4UpInNm() difference = np.sum(np.abs(tmp1-tmp2)) self.assertEqual(difference, 0) # Run it self.algoExp.runIt(self.I1, self.I2, self.opticalModel, tol=1e-3) # Check the value Zk = self.algoExp.getZer4UpInNm() self.assertEqual(int(Zk[7]), -192) # Reset and check the calculation again fieldXY = [self.I1.fieldX, self.I1.fieldY] self.I1.setImg(fieldXY, self.I1.getDefocalType(), image=self.I1.getImgInit()) self.I2.setImg(fieldXY, self.I2.getDefocalType(), image=self.I2.getImgInit()) self.algoExp.reset() self.algoExp.runIt(self.I1, self.I2, self.opticalModel, tol=1e-3) Zk = self.algoExp.getZer4UpInNm() self.assertEqual(int(Zk[7]), -192) def testRunItOfFft(self): self.algoFft.runIt(self.I1, self.I2, self.opticalModel, tol=1e-3) zk = self.algoFft.getZer4UpInNm() self.assertEqual(int(zk[7]), -192)