From 1536ae07c2296d36a3ffd1c3e5a9bfcf5ffb23c4 Mon Sep 17 00:00:00 2001 From: John Franklin Crenshaw Date: Mon, 11 Nov 2024 15:06:35 -0800 Subject: [PATCH] nollIndices configurable from tasks. --- python/lsst/ts/wep/task/calcZernikesTask.py | 16 +++++----- .../lsst/ts/wep/task/estimateZernikesBase.py | 19 ++++-------- tests/task/test_calcZernikesTieTaskCwfs.py | 29 +++++++++++++++++++ 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/python/lsst/ts/wep/task/calcZernikesTask.py b/python/lsst/ts/wep/task/calcZernikesTask.py index 87eeaeba4..b0990fc43 100644 --- a/python/lsst/ts/wep/task/calcZernikesTask.py +++ b/python/lsst/ts/wep/task/calcZernikesTask.py @@ -131,7 +131,7 @@ def __init__(self, **kwargs) -> None: # Create subtasks self.estimateZernikes = self.config.estimateZernikes self.makeSubtask("estimateZernikes") - self.maxNollIndex = self.estimateZernikes.config.maxNollIndex + self.nollIndices = self.estimateZernikes.config.nollIndices self.combineZernikes = self.config.combineZernikes self.makeSubtask("combineZernikes") @@ -165,7 +165,7 @@ def initZkTable(self) -> QTable: ("intra_frac_bad_pix", " QTable: table["extra_field"].unit = u.deg table["intra_centroid"].unit = u.pixel table["extra_centroid"].unit = u.pixel - for j in range(4, self.maxNollIndex + 1): + for j in self.nollIndices: table[f"Z{j}"].unit = u.nm return table @@ -215,8 +215,8 @@ def createZkTable( "label": "average", "used": True, **{ - f"Z{j}": zkCoeffCombined.combinedZernikes[j - 4] * u.micron - for j in range(4, self.maxNollIndex + 1) + f"Z{j}": zkCoeffCombined.combinedZernikes[i] * u.micron + for i, j in enumerate(self.nollIndices) }, "intra_field": np.nan, "extra_field": np.nan, @@ -248,7 +248,7 @@ def createZkTable( row["label"] = f"pair{i+1}" row["used"] = not flag row.update( - {f"Z{j}": zk[j - 4] * u.micron for j in range(4, self.maxNollIndex + 1)} + {f"Z{j}": zk[i] * u.micron for i, j in enumerate(self.nollIndices)} ) row["intra_field"] = ( (np.array(np.nan, dtype=pos2f_dtype) * u.deg) @@ -357,8 +357,8 @@ def empty(self) -> pipeBase.Struct: "DEFOCAL_TYPE", ] return pipeBase.Struct( - outputZernikesRaw=np.atleast_2d(np.full(self.maxNollIndex - 3, np.nan)), - outputZernikesAvg=np.atleast_2d(np.full(self.maxNollIndex - 3, np.nan)), + outputZernikesRaw=np.atleast_2d(np.full(len(self.nollIndices), np.nan)), + outputZernikesAvg=np.atleast_2d(np.full(len(self.nollIndices), np.nan)), zernikes=self.initZkTable(), donutQualityTable=QTable({name: [] for name in qualityTableCols}), ) diff --git a/python/lsst/ts/wep/task/estimateZernikesBase.py b/python/lsst/ts/wep/task/estimateZernikesBase.py index 34893b018..5e10fd581 100644 --- a/python/lsst/ts/wep/task/estimateZernikesBase.py +++ b/python/lsst/ts/wep/task/estimateZernikesBase.py @@ -45,10 +45,11 @@ class EstimateZernikesBaseConfig(pexConfig.Config): dtype=str, optional=True, ) - maxNollIndex = pexConfig.Field( + nollIndices = pexConfig.ListField( dtype=int, - default=28, - doc="The maximum Zernike Noll index estimated.", + default=tuple(range(4, 29)), + doc="Noll indices for which you wish to estimate Zernike coefficients. " + + "Note these values must be unique, ascending, and >= 4.", ) startWithIntrinsic = pexConfig.Field( dtype=bool, @@ -60,15 +61,6 @@ class EstimateZernikesBaseConfig(pexConfig.Config): default=False, doc="If True, returns wavefront deviation. If False, returns full OPD.", ) - return4Up = pexConfig.Field( - dtype=bool, - default=True, - doc="If True, the returned Zernike coefficients start with Noll index 4. " - + "If False, they follow the Galsim convention of starting with index 0 " - + "(which is meaningless), so the array index of the output corresponds " - + "to the Noll index. In this case, indices 0-3 are always set to zero, " - + "because they are not estimated by our pipeline.", - ) binning = pexConfig.Field( dtype=int, default=1, @@ -260,10 +252,9 @@ def run( algoName=self.wfAlgoName, algoConfig=self.wfAlgoConfig, instConfig=instrument, - jmax=self.config.maxNollIndex, + nollIndices=self.config.nollIndices, startWithIntrinsic=self.config.startWithIntrinsic, returnWfDev=self.config.returnWfDev, - return4Up=self.config.return4Up, units="um", saveHistory=self.config.saveHistory, ) diff --git a/tests/task/test_calcZernikesTieTaskCwfs.py b/tests/task/test_calcZernikesTieTaskCwfs.py index 3005f82bd..864776d5e 100644 --- a/tests/task/test_calcZernikesTieTaskCwfs.py +++ b/tests/task/test_calcZernikesTieTaskCwfs.py @@ -281,3 +281,32 @@ def testUnevenPairs(self): # Now estimate Zernikes self.task.run(stampsExtra, stampsIntra) + + def testNollIndices(self): + # Load the stamps + donutStampDir = os.path.join(self.testDataDir, "donutImg", "donutStamps") + donutStampsExtra = DonutStamps.readFits( + os.path.join(donutStampDir, "R04_SW0_donutStamps.fits") + ) + donutStampsIntra = DonutStamps.readFits( + os.path.join(donutStampDir, "R04_SW1_donutStamps.fits") + ) + + # Estimate Zernikes 4, 5, 6 + self.task.config.estimateZernikes.nollIndices = [4, 5, 6] + zk0 = self.task.estimateZernikes.run( + donutStampsExtra, donutStampsIntra + ).zernikes[0] + + # Estimate Zernikes 4, 5, 6, 10, 11 + self.task.config.estimateZernikes.nollIndices = [4, 5, 6, 10, 11] + zk1 = self.task.estimateZernikes.run( + donutStampsExtra, donutStampsIntra + ).zernikes[0] + + # Check lengths + self.assertEqual(len(zk0), 3) + self.assertEqual(len(zk1), 5) + + # Check that 4, 5, 6 are independent of 10, 11 + self.assertTrue(np.all(np.abs(zk1[:3] - zk0) < 0.035))