def test_PolyMapNoTransform(self): """Test constructing a PolyMap with neither forward nor inverse coefficients """ coeff_f = np.array([], dtype=float) coeff_f.shape = (0, 4) coeff_i = np.array([], dtype=float) coeff_i.shape = (0, 3) with self.assertRaises(ValueError): ast.PolyMap(coeff_f, coeff_i) with self.assertRaises(ValueError): ast.PolyMap(coeff_f, 3)
def test_PolyMapDM10496(self): """Test for a segfault when simplifying a SeriesMap We saw an intermittent segfault when simplifying a SeriesMap consisting of the inverse of PolyMap with 2 inputs and one output followed by its inverse (which should simplify to a UnitMap with one input and one output). David Berry fixed this bug in AST 2017-05-10. I tried this test on an older version of astshim and found that it triggering a segfault nearly every time. """ coeff_f = np.array([ [-1.1, 1, 2, 0], [1.3, 1, 3, 1], ]) coeff_i = np.array([ [1.6, 1, 3], [-3.6, 2, 1], ]) # execute many times to increase the odds of a segfault for i in range(1000): amap = ast.PolyMap(coeff_f, coeff_i) amapinv = amap.getInverse() cmp2 = amapinv.then(amap) result = cmp2.simplify() self.assertIsInstance(result, ast.UnitMap)
def test_error_handling(self): """Test handling of AST errors """ # To introduce an AST error construct a PolyMap with no inverse mapping # and then try to transform in the inverse direction. coeff_f = np.array([ [1.2, 1, 2, 0], [-0.5, 1, 1, 1], [1.0, 2, 0, 1], ]) pm = ast.PolyMap(coeff_f, 2, "IterInverse=0") indata = np.array([ [1.0, 2.0, 3.0], [0.0, 1.0, 2.0], ]) # make sure the error string contains "Error" try: pm.applyInverse(indata) except RuntimeError as e: self.assertEqual(e.args[0].count("Error"), 1) print(e) # cause another error and make sure the first error message was purged try: pm.applyInverse(indata) except RuntimeError as e: self.assertEqual(e.args[0].count("Error"), 1)
def test_PolyMapPolyMapUnivertible(self): """Test polyTran on a PolyMap without a single-valued inverse The equation is y = x^2 - x^3, whose inverse has 3 values between roughly -0.66 and 2.0 """ coeff_f = np.array([ [2.0, 1, 2], [-1.0, 1, 3], ]) pm = ast.PolyMap(coeff_f, 1, "IterInverse=1") self.checkBasicSimplify(pm) self.checkCopy(pm) indata = np.array([-0.5, 0.5, 1.1, 1.8]) pred_outdata = (2.0 * indata.T**2 - indata.T**3).T outdata = pm.applyForward(indata) npt.assert_allclose(outdata, pred_outdata) # the iterative inverse should give valid values indata_iterative = pm.applyInverse(outdata) outdata_roundtrip = pm.applyForward(indata_iterative) npt.assert_allclose(outdata, outdata_roundtrip) self.checkMappingPersistence(pm, indata) with self.assertRaises(RuntimeError): # includes the range where the inverse has multiple values, # so no inverse is possible pm.polyTran(False, 1e-3, 1e-3, 10, [-1.0], [2.5])
def makeHscDistortion(config): """Make an HSC distortion transform Note that inverse coefficients provided, but they are not accurate enough to use: test_distortion.py reports an error of 2.8 pixels (HSC uses pixels for its focal plane units) when transforming from pupil to focal plane. That explains why the original HSC model uses the inverse coefficients in conjunction with iteration. Parameters ---------- config: `lsst.obs.subaru.HscDistortionConfig` Distortion coefficients Returns ------- focalPlaneToPupil: `lsst.afw.geom.TransformPoint2ToPoint2` Transform from focal plane to field angle coordinates """ forwardCoeffs = makeAstPolyMapCoeffs(config.ccdToSkyOrder, config.xCcdToSky, config.yCcdToSky) # Note that the actual error can be somewhat larger than TolInverse; # the max error I have seen is less than 2, so I scale conservatively ccdToSky = ast.PolyMap(forwardCoeffs, 2, "IterInverse=1, TolInverse=%s, NIterInverse=%s" % (config.tolerance / 2.0, config.maxIter)) plateScaleAngle = config.plateScale * arcseconds fullMapping = ccdToSky.then(ast.ZoomMap(2, plateScaleAngle.asRadians())) return TransformPoint2ToPoint2(fullMapping)
def test_PolyMapPolyTran(self): coeff_f = np.array([[1., 1, 1, 0], [1., 1, 0, 1], [1., 2, 1, 0], [-1., 2, 0, 1]]) coeff_i = np.array([ [0.5, 1, 1, 0], [0.5, 1, 0, 1], [0.5, 2, 1, 0], [-0.5, 2, 0, 1], ]) pm = ast.PolyMap(coeff_f, coeff_i) indata = np.array([ [1.0, 2.0, 3.0], [0.0, 1.0, 2.0], ]) outdata = pm.applyForward(indata) # create a PolyMap with an identical forward transform and a fit inverse forward = False pm2 = pm.polyTran(forward, 1.0E-10, 1.0E-10, 4, [-1.0, -1.0], [1.0, 1.0]) outdata2 = pm2.applyForward(indata) npt.assert_equal(outdata, outdata2) indata2 = pm2.applyInverse(outdata) npt.assert_allclose(indata, indata2, atol=1.0e-10) self.checkMappingPersistence(pm, indata) self.checkMappingPersistence(pm2, indata)
def test_polyMapAttributes(self): coeff_f = np.array([ [1.2, 1, 2, 0], [-0.5, 1, 1, 1], [1.0, 2, 0, 1], ]) pm = ast.PolyMap(coeff_f, 2, "IterInverse=1, NIterInverse=6, TolInverse=1.2e-7") self.assertIsInstance(pm, ast.Object) self.assertIsInstance(pm, ast.Mapping) self.assertIsInstance(pm, ast.PolyMap) self.assertEqual(pm.nIn, 2) self.assertEqual(pm.nOut, 2) self.assertTrue(pm.iterInverse) self.assertEqual(pm.nIterInverse, 6) self.assertAlmostEqual(pm.tolInverse, 1.2E-7) self.assertTrue(pm.hasForward) self.assertTrue(pm.hasInverse) indata = np.array([ [1.0, 2.0, 3.0], [0.0, 1.0, 2.0], ]) outdata = pm.applyForward(indata) xin, yin = indata pred_xout = (1.2 * xin * xin) - (0.5 * yin * xin) pred_yout = yin xout, yout = outdata npt.assert_allclose(xout, pred_xout) npt.assert_allclose(yout, pred_yout) indata_roundtrip = pm.applyInverse(outdata) npt.assert_allclose(indata, indata_roundtrip, atol=1.0e-6) self.checkMappingPersistence(pm, indata)
def test_QuadApprox(self): # simple parabola coeff_f = np.array([ [0.5, 1, 2, 0], [0.5, 1, 0, 2], ], dtype=float) polymap = ast.PolyMap(coeff_f, 1) qa = ast.QuadApprox(polymap, [-1, -1], [1, 1], 3, 3) self.assertAlmostEqual(qa.rms, 0) self.assertEqual(len(qa.fit), 6) assert_allclose(qa.fit, [0, 0, 0, 0, 0.5, 0.5])
def test_PolyMapIterInverseDominates(self): """Test that IterInverse dominates inverse coefficients for applyInverse """ coeff_f = np.array([ [1., 1, 1], ]) # these coefficients don't match coeff_f, in that the inverse mapping # does not undo the forward mapping (as proven below) coeff_i = np.array([ [25., 1, 2], ]) polyMap = ast.PolyMap(coeff_f, coeff_i, "IterInverse=1") indata = np.array([-0.5, 0.5, 1.1, 1.8]) outdata = polyMap.applyForward(indata) indata_roundtrip = polyMap.applyInverse(outdata) npt.assert_allclose(indata, indata_roundtrip) # prove that without the iterative inverse the PolyMap does not invert correctly polyMap2 = ast.PolyMap(coeff_f, coeff_i) indata_roundtrip2 = polyMap2.applyInverse(outdata) self.assertFalse(np.allclose(indata, indata_roundtrip2))
def test_PolyMapPolyTranIterInverse(self): """Test PolyTran on a PolyMap that has an iterative inverse The result should use the fit inverse, not the iterative inverse """ coeff_f = np.array([ [1., 1, 1], ]) for polyMap in ( ast.PolyMap(coeff_f, 1, "IterInverse=1"), ast.PolyMap(coeff_f, coeff_f, "IterInverse=1"), ): # make sure IterInverse is True and set self.assertTrue(polyMap.iterInverse) self.assertTrue(polyMap.test("IterInverse")) # fit inverse; this should clear iterInverse polyMapFitInv = polyMap.polyTran(False, 1.0E-10, 1.0E-10, 4, [-1.0], [1.0]) self.assertFalse(polyMapFitInv.iterInverse) self.assertFalse(polyMapFitInv.test("IterInverse")) # Fit forward direction of inverted mapping; this should also # clear IterInverse. polyMapInvFitFwd = polyMap.inverted().polyTran( True, 1.0E-10, 1.0E-10, 4, [-1.0], [1.0]) self.assertFalse(polyMapInvFitFwd.iterInverse) self.assertFalse(polyMapFitInv.test("IterInverse")) # cannot fit forward because inverse is iterative with self.assertRaises(ValueError): polyMap.polyTran(True, 1.0E-10, 1.0E-10, 4, [-1.0], [1.0]) # Cannot fit inverse of inverted mapping because forward is # iterative. with self.assertRaises(ValueError): polyMap.inverted().polyTran(False, 1.0E-10, 1.0E-10, 4, [-1.0], [1.0])
def test_MakeRadialMappingForward(self): """Test a radial_map mapping that only has a forward transform """ coeff_f = np.array([ [5.0, 1, 1], [-0.12, 1, 2], ]) mapping1d = ast.PolyMap(coeff_f, 1) self.assertTrue(mapping1d.hasForward) self.assertFalse(mapping1d.hasInverse) # If center = [0.0] then the radial_map mapping is identical to # mapping1d. radial_map = ast.makeRadialMapping([0], mapping1d) self.assertTrue(radial_map.hasForward) self.assertFalse(radial_map.hasInverse) in_data1 = self.in_data_full[0] npt.assert_allclose(radial_map.applyForward(in_data1), mapping1d.applyForward(in_data1)) for center in ( [0.0], [1.1], [0.0, 0.0], [-5.5, 4.7], [0.0, 0.0, 0.0], [1.1, 2.2, -3.3], ): naxes = len(center) center_reshaped = np.expand_dims(center, 1) in_data = self.in_data_full[0:naxes] radial_map = ast.makeRadialMapping(center, mapping1d) self.assertTrue(radial_map.hasForward) self.assertFalse(radial_map.hasInverse) # compute desired output in_from_center = in_data - center_reshaped in_norm = np.linalg.norm(in_from_center, axis=0) with np.errstate(invalid='ignore'): unit_vector = np.where(in_norm != 0, in_from_center / in_norm, in_from_center) out_norm = mapping1d.applyForward(in_norm) out_from_center = unit_vector * out_norm desired_out_data = out_from_center + center_reshaped out_data = radial_map.applyForward(in_data) npt.assert_allclose(out_data, desired_out_data)
def setUp(self): # Make fake sources self.nSources = 10 self.bbox = geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(1024, 1153)) self.xyLoc = 100 dataset = measTests.TestDataset(self.bbox) for srcIdx in range(self.nSources): dataset.addSource(100000.0, geom.Point2D(srcIdx*self.xyLoc, srcIdx*self.xyLoc)) schema = dataset.makeMinimalSchema() schema.addField("base_PixelFlags_flag", type="Flag") schema.addField("base_PixelFlags_flag_offimage", type="Flag") self.exposure, catalog = dataset.realize( 10.0, schema, randomSeed=1234) for src in catalog: src.setCoord(self.exposure.getWcs().pixelToSky(src.getCentroid())) # Non-invertible WCS to test robustness to distortions. # Coefficients transform (x - 1e-7*x^3 -> x, y -> y); see docs for PolyMap. pixelCoeffs = np.array([[-1.0e-7, 1, 3, 0], [1.0, 1, 1, 0], [1.0, 2, 0, 1], ]) self.exposure.setWcs(afwGeom.makeModifiedWcs( afwGeom.TransformPoint2ToPoint2(ast.PolyMap(pixelCoeffs, 2, options="IterInverse=1")), self.exposure.wcs, modifyActualPixels=False )) # Convert to task required format self.testDiaSources = catalog.asAstropy().to_pandas() self.testDiaSources.rename(columns={"coord_ra": "ra", "coord_dec": "decl"}, inplace=True,) self.testDiaSources.loc[:, "ra"] = np.rad2deg(self.testDiaSources["ra"]) self.testDiaSources.loc[:, "decl"] = np.rad2deg(self.testDiaSources["decl"]) self.testDiaSources["ssObjectId"] = 0 # Grab a subset to treat as solar system objects self.testSsObjects = self.testDiaSources[2:8].reset_index() # Assign them ids starting from 1. self.testSsObjects.loc[:, "ssObjectId"] = np.arange( 1, len(self.testSsObjects) + 1, dtype=int,) self.testSsObjects["Err(arcsec)"] = np.ones(len(self.testSsObjects))
def test_PolyMapEmptyInverseCoeffs(self): """Test constructing a PolyMap with empty inverse coefficients """ coeff_f = np.array([[1., 1, 1, 0], [1., 1, 0, 1], [1., 2, 1, 0], [-1., 2, 0, 1]]) coeff_i = np.array([], dtype=float) coeff_i.shape = (0, 4) pm = astshim.PolyMap(coeff_f, coeff_i) self.assertEqual(pm.nIn, 2) self.assertEqual(pm.nOut, 2) self.checkBasicSimplify(pm) self.checkCopy(pm) self.checkPersistence(pm) self.assertTrue(pm.hasForward) self.assertFalse(pm.hasInverse) self.assertFalse(pm.iterInverse)
def test_PolyMapEmptyForwardCoeffs(self): """Test constructing a PolyMap with empty forward coefficients """ coeff_f = np.array([], dtype=float) coeff_f.shape = (0, 4) coeff_i = np.array([ [0.5, 1, 1, 0], [0.5, 1, 0, 1], [0.5, 2, 1, 0], [-0.5, 2, 0, 1], ]) pm = ast.PolyMap(coeff_f, coeff_i) self.assertEqual(pm.nIn, 2) self.assertEqual(pm.nOut, 2) self.checkBasicSimplify(pm) self.checkCopy(pm) self.assertFalse(pm.hasForward) self.assertTrue(pm.hasInverse) self.assertFalse(pm.iterInverse)
def makeSipIwcToPixel(metadata): """Make an IWC to pixel transform with SIP distortion from FITS-WCS metadata This function is primarily intended for unit tests. IWC is intermediate world coordinates, as described in the FITS papers. Parameters ---------- metadata : lsst.daf.base.PropertySet FITS metadata describing a WCS with inverse SIP coefficients Returns ------- lsst.afw.geom.TransformPoint2ToPoint2 Transform from IWC position to pixel position (zero-based) in the forward direction. The inverse direction is not defined. Notes ----- The inverse SIP terms APn_m, BPn_m are polynomial coefficients x^n y^m for computing transformed x, y respectively. If we call the resulting polynomial inverseSipPolynomial, the returned transformation is: pixelPosition = pixel origin + uv + inverseSipPolynomial(uv) where uv = inverseCdMatrix * iwcPosition """ crpix = (metadata.getScalar("CRPIX1") - 1, metadata.getScalar("CRPIX2") - 1) pixelRelativeToAbsoluteMap = ast.ShiftMap(crpix) cdMatrix = getCdMatrixFromMetadata(metadata) cdMatrixMap = ast.MatrixMap(cdMatrix.copy()) coeffList = makeSipPolyMapCoeffs(metadata, "AP") + makeSipPolyMapCoeffs( metadata, "BP") coeffArr = np.array(coeffList, dtype=float) sipPolyMap = ast.PolyMap(coeffArr, 2, "IterInverse=0") iwcToPixelMap = cdMatrixMap.inverted().then(sipPolyMap).then( pixelRelativeToAbsoluteMap) return afwGeom.TransformPoint2ToPoint2(iwcToPixelMap)
def setUp(self): self.longMessage = True # an arbitrary bounding box (not that this kind of field cares) self.bbox = lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(-3, 4), lsst.afw.geom.Extent2I(5, 30)) # a list of points contained in the bbox self.pointList = lsst.afw.geom.Box2D(self.bbox).getCorners() self.pointList.append(lsst.afw.geom.Box2D(self.bbox).getCenter()) self.xList = np.array([p[0] for p in self.pointList]) self.yList = np.array([p[1] for p in self.pointList]) # a simple polynomial mapping coeff_f = np.array([ [1.5, 1, 0, 0], [-0.5, 1, 1, 0], [1.0, 1, 0, 1], ]) polyMap = astshim.PolyMap(coeff_f, 1) self.transform = lsst.afw.geom.TransformPoint2ToGeneric(polyMap) self.boundedField = TransformBoundedField(self.bbox, self.transform)
def test_PolyMapIterativeInverse(self): """Test a unidirectional polymap with its default iterative inverse """ coeff_f = np.array([ [1.2, 1, 2, 0], [-0.5, 1, 1, 1], [1.0, 2, 0, 1], ]) pm = ast.PolyMap(coeff_f, 2, "IterInverse=1") self.assertIsInstance(pm, ast.Object) self.assertIsInstance(pm, ast.Mapping) self.assertIsInstance(pm, ast.PolyMap) self.assertEqual(pm.nIn, 2) self.assertEqual(pm.nOut, 2) self.assertTrue(pm.iterInverse) self.assertEqual(pm.nIterInverse, 4) self.assertAlmostEqual(pm.tolInverse, 1.0E-6) self.assertTrue(pm.hasForward) self.assertTrue(pm.hasInverse) self.checkBasicSimplify(pm) self.checkCopy(pm) indata = np.array([ [1.0, 2.0, 3.0], [0.0, 1.0, 2.0], ]) outdata = pm.applyForward(indata) xin, yin = indata pred_xout = (1.2 * xin * xin) - (0.5 * yin * xin) pred_yout = yin xout, yout = outdata npt.assert_allclose(xout, pred_xout) npt.assert_allclose(yout, pred_yout) indata_roundtrip = pm.applyInverse(outdata) npt.assert_allclose(indata, indata_roundtrip, atol=1.0e-4) self.checkMappingPersistence(pm, indata)
def test_PolyMapPolyTranNontrivial(self): """Test PolyMap.polyTran on a non-trivial case """ # Approximate "field angle to focal plane" transformation coefficients # for LSST thus the domain of the forward direction is # 1.75 degrees = 0.0305 radians. # The camera has 10 um pixels = 0.01 mm # The desired accuracy of the inverse transformation is # 0.001 pixels = 1e-5 mm = 9.69e-10 radians. plateScaleRad = 9.69627362219072e-05 # radians per mm radialCoeff = np.array([0.0, 1.0, 0.0, 0.925]) / plateScaleRad polyCoeffs = [] for i, coeff in enumerate(radialCoeff): polyCoeffs.append((coeff, 1, i)) polyCoeffs = np.array(polyCoeffs) fieldAngleToFocalPlane = ast.PolyMap(polyCoeffs, 1) atolRad = 1.0e-9 fieldAngleToFocalPlane2 = fieldAngleToFocalPlane.polyTran( forward=False, acc=atolRad, maxacc=atolRad, maxorder=10, lbnd=[0], ubnd=[0.0305]) fieldAngle = np.linspace(0, 0.0305, 100) focalPlane = fieldAngleToFocalPlane.applyForward(fieldAngle) fieldAngleRoundTrip = fieldAngleToFocalPlane2.applyInverse(focalPlane) npt.assert_allclose(fieldAngle, fieldAngleRoundTrip, atol=atolRad) # Verify that polyTran cannot fit the inverse when maxorder is # too small. with self.assertRaises(RuntimeError): fieldAngleToFocalPlane.polyTran(forward=False, acc=atolRad, maxacc=atolRad, maxorder=3, lbnd=[0], ubnd=[0.0305])
def makeSipPixelToIwc(metadata): """Make a pixel to IWC transform with SIP distortion from FITS-WCS metadata This function is primarily intended for unit tests. IWC is intermediate world coordinates, as described in the FITS papers. Parameters ---------- metadata : lsst.daf.base.PropertySet FITS metadata describing a WCS with forward SIP coefficients Returns ------- lsst.afw.geom.TransformPoint2ToPoint2 Transform from pixel position (zero-based) to IWC position in the forward direction. The inverse direction is not defined. Notes ----- The forward SIP terms An_m, Bn_m are polynomial coefficients x^n y^m for computing transformed x, y respectively. If we call the resulting polynomial sipPolynomial, the returned transformation is: iwcPosition = cdMatrix * (dxy + sipPolynomial(dxy)) where dxy = pixelPosition - pixelOrigin """ crpix = (metadata.get("CRPIX1") - 1, metadata.get("CRPIX2") - 1) pixelAbsoluteToRelativeMap = ast.ShiftMap(crpix).getInverse() cdMatrix = getCdMatrixFromMetadata(metadata) cdMatrixMap = ast.MatrixMap(cdMatrix.copy()) coeffList = makeSipPolyMapCoeffs(metadata, "A") + makeSipPolyMapCoeffs( metadata, "B") coeffArr = np.array(coeffList, dtype=float) sipPolyMap = ast.PolyMap(coeffArr, 2, "IterInverse=0") pixelToIwcMap = pixelAbsoluteToRelativeMap.then(sipPolyMap).then( cdMatrixMap) return afwGeom.TransformPoint2ToPoint2(pixelToIwcMap)
def test_polyMapNoInverse(self): """Test a unidirectional polymap with no numeric inverse """ coeff_f = np.array([ [1.2, 1, 2, 0], [-0.5, 1, 1, 1], [1.0, 2, 0, 1], ]) pm = ast.PolyMap(coeff_f, 2) self.assertIsInstance(pm, ast.PolyMap) self.assertEqual(pm.nIn, 2) self.assertEqual(pm.nOut, 2) self.assertTrue(pm.hasForward) self.assertFalse(pm.hasInverse) self.assertFalse(pm.iterInverse) indata = np.array([ [1.0, 2.0, 3.0], [0.0, 1.0, 2.0], ]) outdata = pm.applyForward(indata) with self.assertRaises(RuntimeError): pm.applyInverse(indata) pminv = pm.getInverse() self.assertFalse(pminv.hasForward) self.assertTrue(pminv.hasInverse) self.assertTrue(pminv.isInverted) self.assertFalse(pm.iterInverse) outdata2 = pminv.applyInverse(indata) # outdata and outdata2 should be identical because inverting # swaps the behavior of applyForward and applyInverse npt.assert_equal(outdata, outdata2) with self.assertRaises(RuntimeError): pminv.applyForward(indata) self.checkMappingPersistence(pm, indata)
def test_PolyMapBidirectional(self): coeff_f = np.array([[1., 1, 1, 0], [1., 1, 0, 1], [1., 2, 1, 0], [-1., 2, 0, 1]]) coeff_i = np.array([ [0.5, 1, 1, 0], [0.5, 1, 0, 1], [0.5, 2, 1, 0], [-0.5, 2, 0, 1], ]) pm = ast.PolyMap(coeff_f, coeff_i) self.assertEqual(pm.nIn, 2) self.assertEqual(pm.nOut, 2) self.checkBasicSimplify(pm) self.checkCopy(pm) indata = np.array([ [1.0, 2.0, 3.0], [0.0, 1.0, 2.0], ]) self.checkRoundTrip(pm, indata) self.checkMappingPersistence(pm, indata)
def testComplexPersistence(self): """Test persistence of a TransformBoundedField whose string representation is huge """ # DM-11964 shows that CFITSIO cannot handle string fields # in binary tables that have more than 28799 characters # make sure the test has plenty of margin minChars = 10 * 28799 degree = 100 # make large enough that len(transform.writeString()) > minChars n_coeffs = (degree + 1) * (degree + 2) // 2 coeffs = np.zeros((n_coeffs, 4), dtype=float) k = 0 for j in range(degree + 1): for i in range(degree - j + 1): coeffs[k][0] = np.random.random() coeffs[k][1] = 1 coeffs[k][2] = i coeffs[k][3] = j k += 1 chebyMap = astshim.PolyMap(coeffs, 1) transform = lsst.afw.geom.TransformPoint2ToGeneric(chebyMap) print("nchar=%s; minChar=%s" % (len(transform.writeString()), minChars)) self.assertGreater(len(transform.writeString()), minChars) complexBoundedField = TransformBoundedField(self.bbox, transform) with lsst.utils.tests.getTempFilePath(".fits") as filename: complexBoundedField.writeFits(filename) readField = TransformBoundedField.readFits(filename) self.assertTrue(complexBoundedField == readField) self.assertFalse(complexBoundedField != readField) self.assertEqual(complexBoundedField, readField) resArr = complexBoundedField.evaluate(self.xList, self.yList) readResArr = readField.evaluate(self.xList, self.yList) assert_allclose(resArr, readResArr) self.assertEqual(readField.getBBox(), self.bbox)