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
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