Beispiel #1
0
class WepPhosimCmpt(object):

    NUM_OF_ZK = 19

    PISTON_INTRA_DIR_NAME = "intra"
    PISTON_EXTRA_DIR_NAME = "extra"

    def __init__(self, phosimDir):
        """Initialization of WEP PhoSim component class.

        WEP: wavefront estimation pipeline.

        Parameters
        ----------
        phosimDir : str
            PhoSim directory.
        """

        self.metr = OpdMetrology()
        self.tele = TeleFacade()

        self.outputDir = ""
        self.outputImgDir = ""
        self.seedNum = 0

        self.phosimParam = {"numPro": 1, "e2ADC": 1}

        self._config(phosimDir)

    def _config(self, phosimDir):
        """Do the configuration of WEP PhoSim component class.

        Parameters
        ----------
        phosimDir : str
            PhoSim directory.
        """

        # Configurate the TeleFacade class
        configDataPath = self._getConfigDataPath()
        teleConfigFilePath = os.path.join(configDataPath, "telescopeConfig",
                                          "GT.inst")
        self.tele.setConfigFile(teleConfigFilePath)

        camDataDir = os.path.join(configDataPath, "camera")
        M1M3dataDir = os.path.join(configDataPath, "M1M3")
        M2dataDir = os.path.join(configDataPath, "M2")
        self.tele.setSubSysConfigDir(camDataDir=camDataDir,
                                     M1M3dataDir=M1M3dataDir,
                                     M2dataDir=M2dataDir,
                                     phosimDir=phosimDir)

        self.tele.setSensorOn(sciSensorOn=True,
                              wfSensorOn=False,
                              guidSensorOn=False)

        # The lsst is used here because the obs_lsst only supports the lsst now
        self.tele.setInstName("lsst15")

    def _getConfigDataPath(self):
        """Get the configuration data path.

        Returns
        -------
        str
            Configuration data path
        """

        configDataPath = os.path.join(getModulePath(), "configData")

        return configDataPath

    def getOpdMetr(self):
        """Get the OPD metrology object.

        OPD: optical path difference.

        Returns
        -------
        OpdMetrology
            OPD metrology object.
        """

        return self.metr

    def setOutputDir(self, outputDir):
        """Set the output directory.

        The output directory will be constructed if there is no existed one.

        Parameters
        ----------
        outputDir : str
            Output directory.
        """

        self._makeDir(outputDir)
        self.outputDir = outputDir

    def _makeDir(self, newDir, exist_ok=True):
        """Make the new directory.

        Super-mkdir; create a leaf directory and all intermediate ones. Works
        like mkdir, except that any intermediate path segment (not just the
        rightmost) will be created if it does not exist.

        Parameters
        ----------
        newDir : str
            New directory.
        exist_ok : bool, optional
            If the target directory already exists, raise an OSError if
            exist_ok is False. Otherwise no exception is raised. (the default
            is True.)
        """

        os.makedirs(newDir, exist_ok=exist_ok)

    def getOutputDir(self):
        """Get the output directory.

        Returns
        -------
        str
            Output directory.
        """

        return self.outputDir

    def setOutputImgDir(self, outputImgDir):
        """Set the output image directory.

        The output image directory will be constructed if there is no existed
        one.

        Parameters
        ----------
        outputImgDir : str
            Output image directory
        """

        self._makeDir(outputImgDir)
        self.outputImgDir = outputImgDir

    def getOutputImgDir(self):
        """Get the output image directory.

        Returns
        -------
        str
            Output image directory
        """

        return self.outputImgDir

    def setSeedNum(self, seedNum):
        """Set the seed number for the M1M3 mirror surface purturbation.

        Parameters
        ----------
        seedNum : int
            Seed number.
        """

        self.seedNum = int(seedNum)

    def getSeedNum(self):
        """Get the seed number for the M1M3 random surface purturbation.

        Returns
        -------
        int or None
            Seed number. None means there is no random purturbation.
        """

        return self.seedNum

    def setPhosimParam(self, numPro=1, e2ADC=1):
        """Set the PhoSim simulation parameters.

        Parameters
        ----------
        numPro : int, optional
            Number of processors. The value should be greater than 1. (the
            default is 1.)
        e2ADC : int, optional
            Whether to generate amplifier images (1 = true, 0 = false) (the
            default is 1.)
        """

        if (numPro > 0):
            self.phosimParam["numPro"] = int(numPro)

        if e2ADC in (0, 1):
            self.phosimParam["e2ADC"] = int(e2ADC)

    def getPhosimParam(self):
        """Get the PhoSim simulation parameters.

        Returns
        -------
        dict
            PhoSim simulation parameters.
        """

        return self.phosimParam

    def setSurveyParam(self,
                       obsId=None,
                       filterType=None,
                       boresight=None,
                       zAngleInDeg=None,
                       rotAngInDeg=None,
                       mjd=None):
        """Set the survey parameters.

        Parameters
        ----------
        obsId : int, optional
            Observation Id. (the default is None.)
        filterType : FilterType, optional
            Active filter type. (the default is None.)
        boresight : tuple, optional
            Telescope boresight in (ra, decl). (the default is None.)
        zAngleInDeg : float, optional
            Zenith angle in degree. (the default is None.)
        rotAngInDeg : float, optional
            Camera rotation angle in degree between -90 and 90 degrees. (the
            default is None.)
        mjd : float, optional
            MJD of observation. (the default is None.)
        """

        self.tele.setSurveyParam(obsId=obsId,
                                 filterType=filterType,
                                 boresight=boresight,
                                 zAngleInDeg=zAngleInDeg,
                                 rotAngInDeg=rotAngInDeg,
                                 mjd=mjd)

    def addOpdFieldXYbyDeg(self, fieldXInDegree, fieldYInDegree):
        """Add the OPD new field X, Y in degree.

        OPD: optical path difference.

        Parameters
        ----------
        fieldXInDegree : float, list, or numpy.ndarray
            New field X in degree.
        fieldYInDegree : float, list, or numpy.ndarray
            New field Y in degree.
        """

        self.metr.addFieldXYbyDeg(fieldXInDegree, fieldYInDegree)

    def accDofInUm(self, dofInUm):
        """Accumulate the aggregated degree of freedom (DOF) in um.

        idx 0-4: M2 dz, dx, dy, rx, ry
        idx 5-9: Cam dz, dx, dy, rx, ry
        idx 10-29: M1M3 20 bending modes
        idx 30-49: M2 20 bending modes

        Parameters
        ----------
        dofInUm : list or numpy.ndarray
            DOF in um.
        """

        self.tele.accDofInUm(dofInUm)

    def setDofInUm(self, dofInUm):
        """Set the accumulated degree of freedom (DOF) in um.

        idx 0-4: M2 dz, dx, dy, rx, ry
        idx 5-9: Cam dz, dx, dy, rx, ry
        idx 10-29: M1M3 20 bending modes
        idx 30-49: M2 20 bending modes

        Parameters
        ----------
        dofInUm : list or numpy.ndarray
            DOF in um.
        """

        self.tele.setDofInUm(dofInUm)

    def getDofInUm(self):
        """Get the accumulated degree of freedom (DOF) in um.

        idx 0-4: M2 dz, dx, dy, rx, ry
        idx 5-9: Cam dz, dx, dy, rx, ry
        idx 10-29: M1M3 20 bending modes
        idx 30-49: M2 20 bending modes

        Returns
        -------
        numpy.ndarray
            DOF in um.
        """

        return self.tele.dofInUm

    def saveDofInUmFileForNextIter(self,
                                   dofInUm,
                                   dofInUmFileName="dofPertInNextIter.mat"):
        """Save the DOF in um data to file for the next iteration.

        DOF: degree of freedom.

        Parameters
        ----------
        dofInUm : list or numpy.ndarray
            DOF in um.
        dofInUmFileName : str, optional
            File name to save the DOF in um. (the default is
            "dofPertInNextIter.mat".)
        """

        filePath = os.path.join(self.outputDir, dofInUmFileName)
        header = "The followings are the DOF in um:"
        np.savetxt(filePath, np.transpose(dofInUm), header=header)

    def runPhoSim(self, argString):
        """Run the PhoSim program.

        Parameters
        ----------
        argString : str 
            Arguments for PhoSim.
        """

        self.tele.runPhoSim(argString)

    def getComCamOpdArgsAndFilesForPhoSim(
            self,
            cmdFileName="opd.cmd",
            instFileName="opd.inst",
            logFileName="opdPhoSim.log",
            cmdSettingFileName="opdDefault.cmd",
            instSettingFileName="opdDefault.inst"):
        """Get the OPD calculation arguments and files of ComCam for the PhoSim
        calculation.

        OPD: optical path difference.
        ComCam: commissioning camera.

        Parameters
        ----------
        cmdFileName : str, optional
            Physical command file name. (the default is "opd.cmd".)
        instFileName : str, optional
            OPD instance file name. (the default is "opd.inst".)
        logFileName : str, optional
            Log file name. (the default is "opdPhoSim.log".)
        cmdSettingFileName : str, optional
            Physical command setting file name. (the default is
            "opdDefault.cmd".)
        instSettingFileName : str, optional
            Instance setting file name. (the default is "opdDefault.inst".)

        Returns
        -------
        str
            Arguments to run the PhoSim.
        """

        # Set the default ComCam OPD field positions
        self.metr.setDefaultComcamGQ()

        argString = self._getOpdArgsAndFilesForPhoSim(
            cmdFileName=cmdFileName,
            instFileName=instFileName,
            logFileName=logFileName,
            cmdSettingFileName=cmdSettingFileName,
            instSettingFileName=instSettingFileName)

        return argString

    def _getOpdArgsAndFilesForPhoSim(self,
                                     cmdFileName="opd.cmd",
                                     instFileName="opd.inst",
                                     logFileName="opdPhoSim.log",
                                     cmdSettingFileName="opdDefault.cmd",
                                     instSettingFileName="opdDefault.inst"):
        """Get the OPD calculation arguments and files for the PhoSim
        calculation.

        OPD: optical path difference.

        Parameters
        ----------
        cmdFileName : str, optional
            Physical command file name. (the default is "opd.cmd".)
        instFileName : str, optional
            OPD instance file name. (the default is "opd.inst".)
        logFileName : str, optional
            Log file name. (the default is "opdPhoSim.log".)
        cmdSettingFileName : str, optional
            Physical command setting file name. (the default is
            "opdDefault.cmd".)
        instSettingFileName : str, optional
            Instance setting file name. (the default is "opdDefault.inst".)

        Returns
        -------
        str
            Arguments to run the PhoSim.
        """

        # Write the command file
        cmdFilePath = self._writePertAndCmdFiles(cmdSettingFileName,
                                                 cmdFileName)

        # Write the instance file
        instSettingFile = self._getInstSettingFilePath(instSettingFileName)
        instFilePath = self.tele.writeOpdInstFile(
            self.outputDir,
            self.metr,
            instSettingFile=instSettingFile,
            instFileName=instFileName)

        # Get the argument to run the PhoSim
        argString = self._getPhoSimArgs(logFileName, instFilePath, cmdFilePath)

        return argString

    def _writePertAndCmdFiles(self, cmdSettingFileName, cmdFileName):
        """Write the physical perturbation and command files.

        Parameters
        ----------
        cmdSettingFileName : str
            Physical command setting file name.
        cmdFileName : str
            Physical command file name.

        Returns
        -------
        str
            Command file path.
        """

        # Write the perturbation file
        pertCmdFileName = "pert.cmd"
        pertCmdFilePath = os.path.join(self.outputDir, pertCmdFileName)
        if (not os.path.exists(pertCmdFilePath)):
            self.tele.writePertBaseOnConfigFile(
                self.outputDir,
                seedNum=self.seedNum,
                saveResMapFig=True,
                pertCmdFileName=pertCmdFileName)

        # Write the physical command file
        configDataPath = self._getConfigDataPath()
        cmdSettingFile = os.path.join(configDataPath, "cmdFile",
                                      cmdSettingFileName)
        cmdFilePath = os.path.join(self.outputDir, cmdFileName)
        if (not os.path.exists(cmdFilePath)):
            self.tele.writeCmdFile(self.outputDir,
                                   cmdSettingFile=cmdSettingFile,
                                   pertFilePath=pertCmdFilePath,
                                   cmdFileName=cmdFileName)

        return cmdFilePath

    def _getInstSettingFilePath(self, instSettingFileName):
        """Get the instance setting file path.

        Parameters
        ----------
        instSettingFileName : str
            Instance setting file name.

        Returns
        -------
        str
            Instance setting file path.
        """

        configDataPath = self._getConfigDataPath()
        instSettingFile = os.path.join(configDataPath, "instFile",
                                       instSettingFileName)

        return instSettingFile

    def _getPhoSimArgs(self, logFileName, instFilePath, cmdFilePath):
        """Get the arguments needed to run the PhoSim.

        Parameters
        ----------
        logFileName : str
            Log file name.
        instFilePath: str
            Instance file path.
        cmdFilePath : str
            Physical command file path.

        Returns
        -------
        str
            Arguments to run the PhoSim.
        """

        numPro = self.phosimParam["numPro"]
        e2ADC = self.phosimParam["e2ADC"]
        logFilePath = os.path.join(self.outputImgDir, logFileName)

        argString = self.tele.getPhoSimArgs(instFilePath,
                                            extraCommandFile=cmdFilePath,
                                            numPro=numPro,
                                            outputDir=self.outputImgDir,
                                            e2ADC=e2ADC,
                                            logFilePath=logFilePath)

        return argString

    def getComCamStarArgsAndFilesForPhoSim(
            self,
            extraObsId,
            intraObsId,
            skySim,
            simSeed=1000,
            cmdSettingFileName="starDefault.cmd",
            instSettingFileName="starSingleExp.inst"):
        """Get the star calculation arguments and files of ComCam for the PhoSim
        calculation.

        Parameters
        ----------
        extraObsId : int
            Extra-focal observation Id.
        intraObsId : int
            Intra-focal observation Id.
        skySim : SkySim
            Sky simulator
        simSeed : int, optional
            Random number seed. (the default is 1000.)
        cmdSettingFileName : str, optional
            Physical command setting file name. (the default is
            "starDefault.cmd".)
        instSettingFileName : {str}, optional
            Instance setting file name. (the default is "starSingleExp.inst".)

        Returns
        -------
        list[str]
            List of arguments to run the PhoSim.
        """

        # Set the intra- and extra-focal related information
        obsIdList = {"-1": extraObsId, "1": intraObsId}
        instFileNameList = {"-1": "starExtra.inst", "1": "starIntra.inst"}
        logFileNameList = {
            "-1": "starExtraPhoSim.log",
            "1": "starIntraPhoSim.log"
        }
        outImgDirNameList = {
            "-1": self.PISTON_EXTRA_DIR_NAME,
            "1": self.PISTON_INTRA_DIR_NAME
        }

        # Write the instance and command files of defocal conditions
        cmdFileName = "star.cmd"
        onFocalDofInUm = self.getDofInUm()
        onFocalOutputImgDir = self.outputImgDir
        argStringList = []
        for ii in (-1, 1):

            # Set the observation ID
            self.setSurveyParam(obsId=obsIdList[str(ii)])

            # Camera piston (Change the unit from mm to um)
            pistonInUm = np.zeros(len(onFocalDofInUm))
            pistonInUm[5] = ii * self.tele.getDefocalDisInMm() * 1e3

            # Set the new DOF that considers the piston motion
            self.setDofInUm(onFocalDofInUm + pistonInUm)

            # Update the output image directory
            outputImgDir = os.path.join(onFocalOutputImgDir,
                                        outImgDirNameList[str(ii)])
            self.setOutputImgDir(outputImgDir)

            # Get the argument to run the phosim
            argString = self.getStarArgsAndFilesForPhoSim(
                skySim,
                cmdFileName=cmdFileName,
                instFileName=instFileNameList[str(ii)],
                logFileName=logFileNameList[str(ii)],
                simSeed=simSeed,
                cmdSettingFileName=cmdSettingFileName,
                instSettingFileName=instSettingFileName)
            argStringList.append(argString)

        # Put the internal state back to the focal plane condition
        self.setDofInUm(onFocalDofInUm)
        self.setOutputImgDir(onFocalOutputImgDir)

        return argStringList

    def getStarArgsAndFilesForPhoSim(self,
                                     skySim,
                                     cmdFileName="star.cmd",
                                     instFileName="star.inst",
                                     logFileName="starPhoSim.log",
                                     simSeed=1000,
                                     cmdSettingFileName="starDefault.cmd",
                                     instSettingFileName="starSingleExp.inst"):
        """Get the star calculation arguments and files for the PhoSim
        calculation.

        Parameters
        ----------
        skySim : SkySim
            Sky simulator
        cmdFileName : str, optional
            Physical command file name. (the default is "star.cmd".)
        instFileName : str, optional
            Star instance file name. (the default is "star.inst".)
        logFileName : str, optional
            Log file name. (the default is "starPhoSim.log".)
        simSeed : int, optional
            Random number seed. (the default is 1000)
        cmdSettingFileName : str, optional
            Physical command setting file name. (the default is
            "starDefault.cmd".)
        instSettingFileName : str, optional
            Instance setting file name. (the default is "starSingleExp.inst".)

        Returns
        -------
        str
            Arguments to run the PhoSim.
        """

        # Write the command file
        cmdFilePath = self._writePertAndCmdFiles(cmdSettingFileName,
                                                 cmdFileName)

        # Write the instance file
        instSettingFile = self._getInstSettingFilePath(instSettingFileName)
        instFilePath = self.tele.writeStarInstFile(
            self.outputDir,
            skySim,
            simSeed=simSeed,
            sedName="sed_flat.txt",
            instSettingFile=instSettingFile,
            instFileName=instFileName)

        # Get the argument to run the PhoSim
        argString = self._getPhoSimArgs(logFileName, instFilePath, cmdFilePath)

        return argString

    def analyzeComCamOpdData(self,
                             zkFileName="opd.zer",
                             pssnFileName="PSSN.txt"):
        """Analyze the ComCam OPD data.

        ComCam: Commissioning camera.
        OPD: optical path difference.
        PSSN: normalized point source sensitivity.

        Parameters
        ----------
        zkFileName : str, optional
            OPD in zk file name. (the default is "opd.zer".)
        pssnFileName : str, optional
            PSSN file name. (the default is "PSSN.txt".)
        """

        self._writeOpdZkFile(zkFileName)
        self._writeOpdPssnFile(pssnFileName)

    def _writeOpdZkFile(self, zkFileName):
        """Write the OPD in zk file.

        OPD: optical path difference.

        Parameters
        ----------
        zkFileName : str
            OPD in zk file name.
        """

        filePath = os.path.join(self.outputImgDir, zkFileName)
        opdData = self._mapOpdToZk()
        header = "The followings are OPD in um from z4 to z22:"
        np.savetxt(filePath, opdData, header=header)

    def _mapOpdToZk(self):
        """Map the OPD to the basis of annular Zernike polynomial (Zk).

        OPD: optical path difference.

        Returns
        -------
        numpy.ndarray
            Zk data from OPD. This is a 2D array. The row is the OPD index and
            the column is z4 to z22 in um. The order of OPD index is based on
            the file name.
        """

        # Get the OPD file list
        opdFileList = self._getOpdFileInDir(self.outputImgDir)

        # Map the OPD to the Zk basis and do the collection
        opdData = np.zeros((len(opdFileList), self.NUM_OF_ZK))
        for idx, opdFile in enumerate(opdFileList):

            # z1 to z22 (22 terms)
            zk = self.metr.getZkFromOpd(opdFitsFile=opdFile)[0]

            # Only need to collect z4 to z22
            initIdx = 3
            opdData[idx, :] = zk[initIdx:initIdx + self.NUM_OF_ZK]

        return opdData

    def _writeOpdPssnFile(self, pssnFileName):
        """Write the OPD PSSN in file.

        OPD: optical path difference.
        PSSN: normalized point source sensitivity.

        Parameters
        ----------
        pssnFileName : str
            PSSN file name.
        """

        filePath = os.path.join(self.outputImgDir, pssnFileName)

        # Calculate the PSSN
        pssnList, gqEffPssn = self._calcComCamOpdPssn()

        # Calculate the FWHM
        effFwhmList, gqEffFwhm = self._calcComCamOpdEffFwhm(pssnList)

        # Append the list to write the data into file
        pssnList.append(gqEffPssn)
        effFwhmList.append(gqEffFwhm)

        # Stack the data
        data = np.vstack((pssnList, effFwhmList))

        # Write to file
        header = "The followings are PSSN and FWHM (in arcsec) data. The final number is the GQ value."
        np.savetxt(filePath, data, header=header)

    def _calcComCamOpdPssn(self):
        """Calculate the ComCam PSSN of OPD.

        ComCam: commissioning camera.
        OPD: optical path difference.
        PSSN: normalized point source sensitivity.
        GQ: gaussian quadrature.

        Returns
        -------
        list
            PSSN list.
        float
            GQ effective PSSN.
        """

        opdFileList = self._getOpdFileInDir(self.outputImgDir)

        wavelengthInUm = self.tele.WAVELENGTH_IN_NM * 1e-3
        pssnList = []
        for opdFile in opdFileList:
            pssn = self.metr.calcPSSN(wavelengthInUm, opdFitsFile=opdFile)
            pssnList.append(pssn)

        # Calculate the GQ effectice PSSN
        self._setComCamWgtRatio()
        gqEffPssn = self.metr.calcGQvalue(pssnList)

        return pssnList, gqEffPssn

    def _getOpdFileInDir(self, opdDir):
        """Get the OPD files in the directory.

        OPD: optical path difference.

        Parameters
        ----------
        opdDir : str
            OPD file directory.

        Returns
        -------
        list
            List of OPD files.
        """

        opdFileList = []
        fileList = self._getFileInDir(opdDir)
        for file in fileList:
            fileName = os.path.basename(file)
            m = re.match(r"\Aopd_\d+_(\d+).fits.gz", fileName)
            if (m is not None):
                opdFileList.append(file)

        return opdFileList

    def _getFileInDir(self, fileDir):
        """Get the files in the directory.

        Parameters
        ----------
        fileDir : str
            File directory.

        Returns
        -------
        list
            List of files.
        """

        fileList = []
        for name in os.listdir(fileDir):
            filePath = os.path.join(fileDir, name)
            if os.path.isfile(filePath):
                fileList.append(filePath)

        return fileList

    def _setComCamWgtRatio(self):
        """Set the ComCam weighting ratio. 

        ComCam: Commissioning camera.
        """

        comcamWtRatio = np.ones(9)
        self.metr.setWeightingRatio(comcamWtRatio)

    def _calcComCamOpdEffFwhm(self, pssnList):
        """Calculate the ComCam effective FWHM of OPD.

        ComCam: commissioning camera.
        FWHM: full width and half maximum.
        PSSN: normalized point source sensitivity.
        GQ: Gaussian quadrature.

        Parameters
        ----------
        pssnList : list
            List of PSSN.

        Returns
        -------
        list
            Effective FWHM list.
        float
            GQ effective FWHM of ComCam.
        """

        # Calculate the list of effective FWHM
        effFwhmList = []
        for pssn in pssnList:
            effFwhm = self.metr.calcFWHMeff(pssn)
            effFwhmList.append(effFwhm)

        # Calculate the GQ effectice FWHM
        self._setComCamWgtRatio()
        gqEffFwhm = self.metr.calcGQvalue(effFwhmList)

        return effFwhmList, gqEffFwhm

    def getZkFromFile(self, zkFileName):
        """Get the zk (z4-z22) from file.

        Parameters
        ----------
        zkFileName : str
            Zk file name.

        Returns
        -------
        numpy.ndarray
            zk matrix. The colunm is z4-z22. The raw is each data point.
        """

        filePath = os.path.join(self.outputImgDir, zkFileName)
        zk = np.loadtxt(filePath)

        return zk

    def getOpdPssnFromFile(self, pssnFileName):
        """Get the OPD PSSN from file.

        OPD: optical path difference.
        PSSN: normalized point source sensitivity.

        Parameters
        ----------
        pssnFileName : str
            PSSN file name.

        Returns
        -------
        numpy.ndarray
            PSSN.
        """

        data = self._getDataOfPssnFile(pssnFileName)
        pssn = data[0, :-1]

        return pssn

    def getOpdGqEffFwhmFromFile(self, pssnFileName):
        """Get the OPD GQ effective FWHM from file.

        OPD: optical path difference.
        GQ: Gaussian quadrature.
        FWHM: full width at half maximum.
        PSSN: normalized point source sensitivity.

        Parameters
        ----------
        pssnFileName : str
            PSSN file name.

        Returns
        -------
        float
            OPD GQ effective FWHM.
        """

        data = self._getDataOfPssnFile(pssnFileName)
        gqEffFwhm = data[1, -1]

        return gqEffFwhm

    def _getDataOfPssnFile(self, pssnFileName):
        """Get the data of the PSSN file.

        PSSN: Normalized point source sensitivity.

        Parameters
        ----------
        pssnFileName : str
            PSSN file name.

        Returns
        -------
        numpy.ndarray
            Data of the PSSN file.
        """

        filePath = os.path.join(self.outputImgDir, pssnFileName)
        data = np.loadtxt(filePath)

        return data

    def repackageComCamImgFromPhoSim(self):
        """Repackage the ComCam amplifier images from PhoSim to the single 16
        extension MEFs for processing.

        ComCam: commissioning camera.
        MEF: multi-extension frames.
        """

        # Make a temp directory
        tmpDirPath = os.path.join(self.outputImgDir, "tmp")
        self._makeDir(tmpDirPath)

        for imgType in (self.PISTON_INTRA_DIR_NAME,
                        self.PISTON_EXTRA_DIR_NAME):

            # Repackage the images to that temp directory
            command = "phosim_repackager.py"
            phosimAmgImgDir = os.path.join(self.outputImgDir, imgType)
            argstring = "%s --out_dir=%s" % (phosimAmgImgDir, tmpDirPath)
            runProgram(command, argstring=argstring)

            # Remove the image data in the original directory
            argString = "-rf %s/lsst_a_*.fits.gz" % phosimAmgImgDir
            runProgram("rm", argstring=argString)

            # Put the repackaged data into the image directory
            argstring = "%s/*.fits %s" % (tmpDirPath, phosimAmgImgDir)
            runProgram("mv", argstring=argstring)

        # Remove the temp directory
        shutil.rmtree(tmpDirPath)

    def reorderAndSaveWfErrFile(self,
                                wfErrMap,
                                refSensorNameList,
                                zkFileName="wfs.zer"):
        """Reorder the wavefront error in the wavefront error map according to
        the reference sensor name list and save to a file.

        The unexisted wavefront error will be a numpy zero array. The unit is
        um.

        Parameters
        ----------
        wfErrMap : dict
            Calculated wavefront error. The dictionary key [str] is the
            abbreviated sensor name (e.g. R22_S11). The dictionary item
            [numpy.ndarray] is the averaged wavefront error (z4-z22) in nm.
        refSensorNameList : list
            Reference sensor name list
        zkFileName : str, optional
            Wavefront error file name. (the default is "wfs.zer".)
        """

        # Get the sensor name that in the wavefront error map
        nameListInWfErrMap = list(wfErrMap.keys())

        # Reorder the wavefront error map based on the reference sensor name
        # list. The wavefront error unit will change from nm to um.
        reorderedWfErrMap = dict()
        for sensorName in refSensorNameList:
            if sensorName in nameListInWfErrMap:
                # Change the unit from nm to um
                wfErr = wfErrMap[sensorName] * 1e-3
            else:
                wfErr = np.zeros(self.NUM_OF_ZK)
            reorderedWfErrMap[sensorName] = wfErr

        # Save the file
        filePath = os.path.join(self.outputImgDir, zkFileName)
        wfsData = self.getWfErrValuesAndStackToMatrix(reorderedWfErrMap)
        header = "The followings are ZK in um from z4 to z22:"
        np.savetxt(filePath, wfsData, header=header)

    def getWfErrValuesAndStackToMatrix(self, wfErrMap):
        """Get the wavefront errors and stack them to be a matrix.

        Parameters
        ----------
        wfErrMap : dict
            Calculated wavefront error. The dictionary key [str] is the
            abbreviated sensor name (e.g. R22_S11). The dictionary item
            [numpy.ndarray] is the averaged wavefront error (z4-z22) in nm. 

        Returns
        -------
        numpy.ndarray
            Wavefront errors as a matrix. The column is z4-z22 in nm. The row is
            the individual sensor. The order is the same as the input of
            wfErrMap.
        """

        valueMatrix = np.empty((0, self.NUM_OF_ZK))
        for wfErr in wfErrMap.values():
            valueMatrix = np.vstack((valueMatrix, wfErr))

        return valueMatrix
Beispiel #2
0
class TestTeleFacade(unittest.TestCase):
    """ Test the TeleFacade class."""

    def setUp(self):

        self.configFilePath = os.path.join(getModulePath(), "configData",
                                           "telescopeConfig", "GT.inst")
        self.tele = TeleFacade(configFilePath=self.configFilePath)

        # Set the subsystem data directory
        camDataDir = os.path.join(getModulePath(), "configData", "camera")
        M1M3dataDir = os.path.join(getModulePath(), "configData", "M1M3")
        M2dataDir = os.path.join(getModulePath(), "configData", "M2")
        self.tele.setSubSysConfigDir(camDataDir=camDataDir,
                                     M1M3dataDir=M1M3dataDir,
                                     M2dataDir=M2dataDir)

        # Set the survey parameters
        obsId = 9006000
        filterType = FilterType.G
        boresight = (0.2, 0.3)
        zAngleInDeg = 27.0912
        rotAngInDeg = np.rad2deg(-1.2323)
        mjd = 59552.3
        self.tele.setSurveyParam(obsId=obsId, filterType=filterType,
                                 boresight=boresight, zAngleInDeg=zAngleInDeg,
                                 rotAngInDeg=rotAngInDeg, mjd=mjd)

        # Set the output dir
        self.outputDir = os.path.join(getModulePath(), "output", "temp")
        os.makedirs(self.outputDir)

    def tearDown(self):

        shutil.rmtree(self.outputDir)

    def testGetDefocalDisInMm(self):

        instName = "comcam13"
        self.tele.setInstName(instName)

        self.assertEqual(self.tele.getDefocalDisInMm(), 1.3)

    def testSetSurveyParamWithCorrectInput(self):

        obsId = 100
        filterType = FilterType.U
        boresight = (10, 20)
        zAngleInDeg = 10.0
        rotAngInDeg = 11.0
        mjd = 4000.0

        tele = TeleFacade()
        tele.setSurveyParam(obsId=obsId, filterType=filterType,
                            boresight=boresight, zAngleInDeg=zAngleInDeg,
                            rotAngInDeg=rotAngInDeg, mjd=mjd)

        self.assertEqual(tele.surveyParam["obsId"], obsId)
        self.assertEqual(tele.surveyParam["filterType"], filterType)
        self.assertEqual(tele.surveyParam["boresight"], boresight)
        self.assertEqual(tele.surveyParam["zAngleInDeg"], zAngleInDeg)
        self.assertEqual(tele.surveyParam["rotAngInDeg"], rotAngInDeg)
        self.assertEqual(tele.surveyParam["mjd"], mjd)

    def testSetSurveyParamWithWrongInput(self):

        defaultObsId = self.tele.surveyParam["obsId"]

        obsId = 1.0
        self.tele.setSurveyParam(obsId=obsId)

        self.assertEqual(self.tele.surveyParam["obsId"], defaultObsId)
        self.assertEqual(self.tele.surveyParam["filterType"], FilterType.G)

    def testSetSensorOnWithCorrectInput(self):

        sciSensorOn = False
        wfSensorOn = False
        guidSensorOn = True

        self.tele.setSensorOn(sciSensorOn=sciSensorOn, wfSensorOn=wfSensorOn,
                              guidSensorOn=guidSensorOn)
        self.assertEqual(self.tele.sensorOn["sciSensorOn"], sciSensorOn)
        self.assertEqual(self.tele.sensorOn["wfSensorOn"], wfSensorOn)
        self.assertEqual(self.tele.sensorOn["guidSensorOn"], guidSensorOn)

    def testSetSensorOnWithWrongInput(self):

        sciSensorOn = 0
        self.tele.setSensorOn(sciSensorOn=sciSensorOn)
        self.assertEqual(self.tele.sensorOn["sciSensorOn"], True)

    def testSetConfigFile(self):

        configFilePath = "NotConfigFilePath"
        self.tele.setConfigFile(configFilePath)

        self.assertEqual(self.tele.configFile, configFilePath)

    def testGetConfigValue(self):

        varName = "M1M3TxGrad"
        value = self.tele.getConfigValue(varName)

        self.assertEqual(value, -0.0894)

    def testGetPhoSimArgs(self):

        instFilePath = "temp.inst"
        argString = self.tele.getPhoSimArgs(instFilePath)

        ansArgString = "%s -i lsst -e 1" % os.path.abspath(instFilePath)

        self.assertEqual(argString, ansArgString)

    def testSetInstName(self):

        instName = "comcam10"
        self.tele.setInstName(instName)

        self.assertEqual(self.tele.surveyParam["instName"], "comcam")
        self.assertEqual(self.tele.getDefocalDisInMm(), 1.0)

        instName = "temp"
        self.tele.setInstName(instName)

        self.assertEqual(self.tele.surveyParam["instName"], "temp")
        self.assertEqual(self.tele.getDefocalDisInMm(), 1.5)

        self.assertRaises(ValueError, self.tele.setInstName, "a10a")

    def testSetDofInUm(self):

        dofInUm = np.random.rand(50)
        self.tele.setDofInUm(dofInUm)
        self.assertEqual(np.sum(np.abs(self.tele.dofInUm - dofInUm)), 0)

    def testAccDofInUm(self):

        dofInUm = np.random.rand(50)
        self.tele.accDofInUm(dofInUm)
        self.assertEqual(np.sum(np.abs(self.tele.dofInUm - dofInUm)), 0)

    def testSetSubSysConfigDir(self):

        tele = TeleFacade(configFilePath=self.configFilePath)
        self.assertEqual(tele.cam, None)
        self.assertEqual(tele.M1M3, None)
        self.assertEqual(tele.M2, None)

        camDataDir = "NotCamDataDir"
        M1M3dataDir = "NotM1M3dataDir"
        M2dataDir = "NotM2dataDir"
        phosimDir = "NotPhosimDir"
        tele.setSubSysConfigDir(camDataDir=camDataDir, M1M3dataDir=M1M3dataDir,
                                M2dataDir=M2dataDir, phosimDir=phosimDir)

        # Check the subsystems are instantiated after setting up the
        # configuration directory.
        self.assertNotEqual(tele.cam, None)
        self.assertNotEqual(tele.M1M3, None)
        self.assertNotEqual(tele.M2, None)

        self.assertEqual(tele.cam.camDataDir, camDataDir)
        self.assertEqual(tele.M1M3.mirrorDataDir, M1M3dataDir)
        self.assertEqual(tele.M2.mirrorDataDir, M2dataDir)
        self.assertEqual(tele.phoSimCommu.phosimDir, phosimDir)

    def testWriteAccDofFile(self):

        dofFilePath = self.tele.writeAccDofFile(self.outputDir)
        dof = np.loadtxt(dofFilePath)

        self.assertEqual(np.sum(dof), 0)
        self.assertEqual(len(dof), 50)

    def testWritePertBaseOnConfigFile(self):

        pertCmdFilePath = self._writePertBaseOnConfigFile(self.outputDir)

        numOfLineInFile = self._getNumOfLineInFile(pertCmdFilePath)
        self.assertEqual(numOfLineInFile, 256)

    def _writePertBaseOnConfigFile(self, outputDir):

        iSim = 6
        pertCmdFilePath = self.tele.writePertBaseOnConfigFile(
            outputDir, seedNum=iSim, M1M3ForceError=0.05, saveResMapFig=True,
            pertCmdFileName="pert.cmd")

        return pertCmdFilePath

    def _getNumOfLineInFile(self, filePath):

        with open(filePath, "r") as file:
            return sum(1 for line in file.readlines())

    def testWriteCmdFile(self):

        starCmdSettingFile = os.path.join(getModulePath(), "configData",
                                          "cmdFile", "starDefault.cmd")

        pertCmdFilePath = self._writePertBaseOnConfigFile(self.outputDir)
        cmdFilePath = self.tele.writeCmdFile(
            self.outputDir, cmdSettingFile=starCmdSettingFile,
            pertFilePath=pertCmdFilePath, cmdFileName="star.cmd")

        numOfLineInFile = self._getNumOfLineInFile(cmdFilePath)
        self.assertEqual(numOfLineInFile, 267)

    def testGetPhoSimCamSurf(self):

        camSurfName = "L1S2zer"
        surfaceType = self.tele._getPhoSimCamSurf(camSurfName)
        self.assertEqual(surfaceType.name, "L1B")
        self.assertRaises(ValueError, self.tele._getPhoSimCamSurf,
                          "L4S2zer")

    def testWriteOpdInstFile(self):

        metr, opdInstSettingFile = self._generateOpd()

        instFilePath = self.tele.writeOpdInstFile(
            self.outputDir, metr, instSettingFile=opdInstSettingFile)

        numOfLineInFile = self._getNumOfLineInFile(instFilePath)
        self.assertEqual(numOfLineInFile, 59)

    def testWriteOpdInstFileWithFilterRef(self):

        self.tele.setSurveyParam(filterType=FilterType.REF)

        metr, opdInstSettingFile = self._generateOpd()
        instFilePath = self.tele.writeOpdInstFile(
            self.outputDir, metr, instSettingFile=opdInstSettingFile)

        numOfLineInFile = self._getNumOfLineInFile(instFilePath)
        self.assertEqual(numOfLineInFile, 59)

    def _generateOpd(self):

        metr = OpdMetrology()
        metr.addFieldXYbyDeg(0, 0)
        opdInstSettingFile = os.path.join(getModulePath(), "configData",
                                          "instFile", "opdDefault.inst")

        return metr, opdInstSettingFile

    def testWriteStarInstFile(self):

        skySim, starInstSettingFile = self._generateFakeSky()

        instFilePath = self.tele.writeStarInstFile(
            self.outputDir, skySim, instSettingFile=starInstSettingFile)

        numOfLineInFile = self._getNumOfLineInFile(instFilePath)
        self.assertEqual(numOfLineInFile, 63)

    def testWriteStarInstFileWithFilterRef(self):

        self.tele.setSurveyParam(filterType=FilterType.REF)

        skySim, starInstSettingFile = self._generateFakeSky()
        instFilePath = self.tele.writeStarInstFile(
            self.outputDir, skySim, instSettingFile=starInstSettingFile)

        numOfLineInFile = self._getNumOfLineInFile(instFilePath)
        self.assertEqual(numOfLineInFile, 63)

    def _generateFakeSky(self):

        skySim = SkySim()
        skySim.addStarByRaDecInDeg(0, 1.0, 1.0, 17.0)

        starInstSettingFile = os.path.join(getModulePath(), "configData",
                                           "instFile", "starDefault.inst")

        return skySim, starInstSettingFile