def addAmp(ampCatalog, i, eparams): """ Add an amplifier to an AmpInfoCatalog @param ampCatalog: An instance of an AmpInfoCatalog object to fill with amp properties @param i which amplifier? (i == 0 ? left : right) @param eparams: Electronic parameters. This is a list of tuples with (i, params), where params is a dictionary of electronic parameters. """ # # Layout of active and overclock pixels in the as-readout data The layout is: # Amp0 || extended | overclock | data || data | overclock | extended || Amp1 # for each row; all rows are identical in drift-scan data # height = 1361 # number of rows in a frame width = 1024 # number of data pixels read out through one amplifier nExtended = 8 # number of pixels in the extended register nOverclock = 32 # number of (horizontal) overclock pixels # # Construct the needed bounding boxes given that geometrical information. # # Note that all the offsets are relative to the origin of this amp, not to its eventual # position in the CCD # record = ampCatalog.addNew() xtot = width + nExtended + nOverclock allPixels = geom.BoxI(geom.PointI(0, 0), geom.ExtentI(xtot, height)) biasSec = geom.BoxI(geom.PointI(nExtended if i == 0 else width, 0), geom.ExtentI(nOverclock, height)) dataSec = geom.BoxI( geom.PointI(nExtended + nOverclock if i == 0 else 0, 0), geom.ExtentI(width, height)) emptyBox = geom.BoxI() bbox = geom.BoxI(geom.PointI(0, 0), geom.ExtentI(width, height)) bbox.shift(geom.Extent2I(width * i, 0)) shiftp = geom.Extent2I(xtot * i, 0) allPixels.shift(shiftp) biasSec.shift(shiftp) dataSec.shift(shiftp) record.setBBox(bbox) record.setRawXYOffset(geom.ExtentI(0, 0)) record.setName('left' if i == 0 else 'right') record.setReadoutCorner(afwTable.LL if i == 0 else afwTable.LR) record.setGain(eparams['gain']) record.setReadNoise(eparams['readNoise']) record.setSaturation(eparams['fullWell']) record.setSuspectLevel(float("nan")) record.setLinearityType(NullLinearityType) record.setLinearityCoeffs([ 1., ]) record.setHasRawInfo(True) record.setRawFlipX(False) record.setRawFlipY(False) record.setRawBBox(allPixels) record.setRawDataBBox(dataSec) record.setRawHorizontalOverscanBBox(biasSec) record.setRawVerticalOverscanBBox(emptyBox) record.setRawPrescanBBox(emptyBox)
def addDefects(exp, nBadCols=10): img = exp.getMaskedImage().getImage() (xsize, ysize) = img.getDimensions() defectList = measAlg.Defects() # set some bad cols and add them to a defect list for xi in numpy.random.randint(0, xsize, nBadCols): yi = numpy.random.randint(0, ysize) xi, yi = int(xi), int(yi) bbox = geom.Box2I(geom.PointI(xi, 0), geom.ExtentI(1, yi+1)) subIm = afwImage.ImageF(img, bbox) subIm.set(1e7) defectList.append(bbox) # set a 15 pixel box of defects at the upper left corner to demonstrate fallbackValue bbox = geom.Box2I(geom.PointI(0, ysize-15), geom.ExtentI(15, 15)) subIm = afwImage.ImageF(img, bbox) subIm.set(1e7) defectList.append(bbox) return defectList
def make_cutout(exposure, x, y, cutout_size=60): """Make a cutout exposure at (x, y) pixel coordinate. exposure: lsst.afw.image.exposure.exposure.ExposureF x: x pixel coordinate y: y pixel coordinate cutout_size: Width(in pixel unit) of the postage stamp , default value is 60 """ cutout_extent = geom.ExtentI(cutout_size, cutout_size) radec = geom.SpherePoint(exposure.getWcs().pixelToSky(x, y)) cutout_image = exposure.getCutout(radec, cutout_extent) return cutout_image
def convertfpM(infile, allPlanes=False): with fits.open(infile) as hdr: hdr[0].header['RUN'] hdr[0].header['CAMCOL'] hdr[0].header['FIELD'] nRows = hdr[0].header['MASKROWS'] nCols = hdr[0].header['MASKCOLS'] hdr[0].header['NPLANE'] names = hdr[-1].data.names if ("attributeName" not in names) or ("Value" not in names): raise LookupError("Missing data in fpM header") planes = hdr[-1].data.field("attributeName").tolist() mask = afwImage.Mask(geom.ExtentI(nCols, nRows)) # Minimal sets of mask planes needed for LSST interpPlane = planes.index("S_MASK_INTERP") + 1 satPlane = planes.index("S_MASK_SATUR") + 1 crPlane = planes.index("S_MASK_CR") + 1 interpBitMask = afwImage.Mask.getPlaneBitMask("INTRP") satBitMask = afwImage.Mask.getPlaneBitMask("SAT") crBitMask = afwImage.Mask.getPlaneBitMask("CR") listToSet = [(interpPlane, interpBitMask), (satPlane, satBitMask), (crPlane, crBitMask)] # Add the rest of the SDSS planes if allPlanes: for plane in ['S_MASK_NOTCHECKED', 'S_MASK_OBJECT', 'S_MASK_BRIGHTOBJECT', 'S_MASK_BINOBJECT', 'S_MASK_CATOBJECT', 'S_MASK_SUBTRACTED', 'S_MASK_GHOST']: idx = planes.index(plane) + 1 planeName = re.sub("S_MASK_", "", plane) mask.addMaskPlane(planeName) planeBitMask = afwImage.Mask.getPlaneBitMask(planeName) listToSet.append((idx, planeBitMask)) for plane, bitmask in listToSet: if len(hdr) < plane: continue if hdr[plane].data is None: continue nmask = len(hdr[plane].data) for i in range(nmask): frow = hdr[plane].data[i] Objmask(frow, bitmask).setMask(mask) return mask
def ccdPixelToAmpPixel(xy, detector): r"""Given an position within a detector return position within an amplifier Parameters ---------- xy : `lsst.geom.PointD` pixel position within detector detector : `lsst.afw.cameraGeom.Detector` The requested detector N.b. all pixel coordinates have the centre of the bottom-left pixel at (0.0, 0.0) Returns ------- amp : `lsst.afw.table.AmpInfoRecord` The amplifier that the pixel lies in ampXY : `lsst.geom.PointI` The pixel coordinate relative to the corner of the single-amp image Raises ------ RuntimeError If the requested pixel doesn't lie on the detector """ found = False for amp in detector: if geom.BoxD(amp.getBBox()).contains(xy): found = True xy = geom.PointI(xy) # pixel coordinates as ints break if not found: raise RuntimeError("Point (%g, %g) does not lie on detector %s" % (xy[0], xy[1], detector.getName())) x, y = xy - amp.getBBox().getBegin() # offset from origin of amp's data segment # Allow for flips (due e.g. to physical location of the amplifiers) w, h = amp.getRawDataBBox().getDimensions() if amp.getRawFlipX(): x = w - x - 1 if amp.getRawFlipY(): y = h - y - 1 dxy = amp.getRawBBox().getBegin() - amp.getRawDataBBox().getBegin() # correction for overscan etc. xy = geom.ExtentI(x, y) - dxy return amp, xy
def display_exposure(exposure, x, y, cutout_size=60, coord_list=None, scale=None, frame=None, show_colorbar=False, title=None, save_name=None): """This function displays the postage stamp of an exposure. The center of the postage stamp is marked by a red circle. We can also overlay blue circles corresponding to the coordinaes given in the coord_list on the postage stamp. exposure: lsst.afw.image.exposure.exposure.ExposureF x: x pixel coordinate y: y pixel coordinate cutout_size: Width(in pixel unit) of the postage stamp , default value is 60 coord_list: A list of coordinates where we can overlay blue circles on the postage stamp scale: [min_val, max_val], set the min value and the max value for display, default is None frame: The frame of the afwDisplay.Display object show_colorbar: Show colorbar of the postage stamp, default is False title: Title of the postage stamp save_name: If provided, the postage stamp will be saved as 'save_name.png' in the current working directory, default if None """ cutout_extent = geom.ExtentI(cutout_size, cutout_size) radec = geom.SpherePoint(exposure.getWcs().pixelToSky(x, y)) cutout_image = exposure.getCutout(radec, cutout_extent) xy = geom.PointI(x, y) display = afwDisplay.Display(frame=frame, backend='matplotlib') if scale: display.scale("linear", scale[0], scale[1]) else: display.scale("linear", "zscale") display.mtv(cutout_image) if show_colorbar: display.show_colorbar() display.dot('o', xy.getX(), xy.getY(), ctype='red') if coord_list: for coord in coord_list: coord_x, coord_y = coord display.dot('o', coord_x, coord_y, ctype='blue') plt.title(title) if save_name: plt.savefig(save_name, dpi=500)
def ampPixelToCcdPixel(x, y, detector, channel): r"""Given a position in a raw amplifier return position on full detector Parameters ---------- x : `int` column on amp segment y : `int` row on amp segment detector : `lsst.afw.cameraGeom.Detector` The requested detector channel: `int` Channel number of amplifier (1-indexed; identical to HDU) Returns ------- ccdX : `int` The column pixel position relative to the corner of the detector ccdY : `int` The row pixel position relative to the corner of the detector """ amp = channelToAmp(detector, channel) rawBBox, rawDataBBox = amp.getRawBBox(), amp.getRawDataBBox() # Allow for flips (due e.g. to physical location of the amplifiers) w, h = rawBBox.getDimensions() if amp.getRawFlipX(): rawBBox.flipLR(w) rawDataBBox.flipLR(w) x = rawBBox.getWidth() - x - 1 if amp.getRawFlipY(): rawBBox.flipTB(h) rawDataBBox.flipTB(h) y = rawBBox.getHeight() - y - 1 dxy = rawBBox.getBegin() - rawDataBBox.getBegin() # correction for overscan etc. return amp.getBBox().getBegin() + dxy + geom.ExtentI(x, y)
def make_cutout_radec(exposure, radec, cutout_size=60): cutout_extent = geom.ExtentI(cutout_size, cutout_size) cutout_image = exposure.getCutout(radec, cutout_extent) return cutout_image
def setUp(self): config = SingleFrameMeasurementTask.ConfigClass() config.slots.apFlux = 'base_CircularApertureFlux_12_0' self.schema = afwTable.SourceTable.makeMinimalSchema() self.measureSources = SingleFrameMeasurementTask(self.schema, config=config) width, height = 110, 301 self.mi = afwImage.MaskedImageF(geom.ExtentI(width, height)) self.mi.set(0) sd = 3 # standard deviation of image self.mi.getVariance().set(sd * sd) self.mi.getMask().addMaskPlane("DETECTED") self.ksize = 31 # size of desired kernel sigma1 = 1.75 sigma2 = 2 * sigma1 self.exposure = afwImage.makeExposure(self.mi) self.exposure.setPsf( measAlg.DoubleGaussianPsf(self.ksize, self.ksize, 1.5 * sigma1, 1, 0.1)) cdMatrix = np.array([1.0, 0.0, 0.0, 1.0]) cdMatrix.shape = (2, 2) wcs = afwGeom.makeSkyWcs(crpix=geom.PointD(0, 0), crval=geom.SpherePoint( 0.0, 0.0, geom.degrees), cdMatrix=cdMatrix) self.exposure.setWcs(wcs) # # Make a kernel with the exactly correct basis functions. # Useful for debugging # basisKernelList = [] for sigma in (sigma1, sigma2): basisKernel = afwMath.AnalyticKernel( self.ksize, self.ksize, afwMath.GaussianFunction2D(sigma, sigma)) basisImage = afwImage.ImageD(basisKernel.getDimensions()) basisKernel.computeImage(basisImage, True) basisImage /= np.sum(basisImage.getArray()) if sigma == sigma1: basisImage0 = basisImage else: basisImage -= basisImage0 basisKernelList.append(afwMath.FixedKernel(basisImage)) order = 1 # 1 => up to linear spFunc = afwMath.PolynomialFunction2D(order) exactKernel = afwMath.LinearCombinationKernel(basisKernelList, spFunc) exactKernel.setSpatialParameters([[1.0, 0, 0], [0.0, 0.5 * 1e-2, 0.2e-2]]) rand = afwMath.Random() # make these tests repeatable by setting seed addNoise = True if addNoise: im = self.mi.getImage() afwMath.randomGaussianImage(im, rand) # N(0, 1) im *= sd # N(0, sd^2) del im xarr, yarr = [], [] for x, y in [ (20, 20), (60, 20), (30, 35), (50, 50), (20, 90), (70, 160), (25, 265), (75, 275), (85, 30), (50, 120), (70, 80), (60, 210), (20, 210), ]: xarr.append(x) yarr.append(y) for x, y in zip(xarr, yarr): dx = rand.uniform() - 0.5 # random (centered) offsets dy = rand.uniform() - 0.5 k = exactKernel.getSpatialFunction(1)( x, y) # functional variation of Kernel ... b = (k * sigma1**2 / ((1 - k) * sigma2**2) ) # ... converted double Gaussian's "b" # flux = 80000 - 20*x - 10*(y/float(height))**2 flux = 80000 * (1 + 0.1 * (rand.uniform() - 0.5)) I0 = flux * (1 + b) / (2 * np.pi * (sigma1**2 + b * sigma2**2)) for iy in range(y - self.ksize // 2, y + self.ksize // 2 + 1): if iy < 0 or iy >= self.mi.getHeight(): continue for ix in range(x - self.ksize // 2, x + self.ksize // 2 + 1): if ix < 0 or ix >= self.mi.getWidth(): continue II = I0 * psfVal(ix, iy, x + dx, y + dy, sigma1, sigma2, b) Isample = rand.poisson(II) if addNoise else II self.mi.image[ix, iy, afwImage.LOCAL] += Isample self.mi.variance[ix, iy, afwImage.LOCAL] += II bbox = geom.BoxI(geom.PointI(0, 0), geom.ExtentI(width, height)) self.cellSet = afwMath.SpatialCellSet(bbox, 100) self.footprintSet = afwDetection.FootprintSet( self.mi, afwDetection.Threshold(100), "DETECTED") self.catalog = self.measure(self.footprintSet, self.exposure) for source in self.catalog: try: cand = measAlg.makePsfCandidate(source, self.exposure) self.cellSet.insertCandidate(cand) except Exception as e: print(e) continue
def assemble_raw(dataId, componentInfo, cls): """Called by the butler to construct the composite type "raw". Note that we still need to define "_raw" and copy various fields over. Parameters ---------- dataId : `lsst.daf.persistence.dataId.DataId` The data ID. componentInfo : `dict` dict containing the components, as defined by the composite definition in the mapper policy. cls : 'object' Unused. Returns ------- exposure : `lsst.afw.image.Exposure` The assembled exposure. """ from lsst.ip.isr import AssembleCcdTask config = AssembleCcdTask.ConfigClass() config.doTrim = False assembleTask = AssembleCcdTask(config=config) ampExps = componentInfo['raw_amp'].obj if len(ampExps) == 0: raise RuntimeError("Unable to read raw_amps for %s" % dataId) ccd = ampExps[0].getDetector() # the same (full, CCD-level) Detector is attached to all ampExps # # Check that the geometry in the metadata matches cameraGeom # logger = lsst.log.Log.getLogger("LsstCamMapper") warned = False for i, (amp, ampExp) in enumerate(zip(ccd, ampExps)): ampMd = ampExp.getMetadata().toDict() if amp.getRawBBox() != ampExp.getBBox(): # Oh dear. cameraGeom is wrong -- probably overscan if amp.getRawDataBBox().getDimensions() != amp.getBBox().getDimensions(): raise RuntimeError("Active area is the wrong size: %s v. %s" % (amp.getRawDataBBox().getDimensions(), amp.getBBox().getDimensions())) if not warned: logger.warn("amp.getRawBBox() != data.getBBox(); patching. (%s v. %s)", amp.getRawBBox(), ampExp.getBBox()) warned = True w, h = ampExp.getBBox().getDimensions() ow, oh = amp.getRawBBox().getDimensions() # "old" (cameraGeom) dimensions # # We could trust the BIASSEC keyword, or we can just assume that # they've changed the number of overscan pixels (serial and/or # parallel). As Jim Chiang points out, the latter is safer # bbox = amp.getRawHorizontalOverscanBBox() hOverscanBBox = geom.BoxI(bbox.getBegin(), geom.ExtentI(w - bbox.getBeginX(), bbox.getHeight())) bbox = amp.getRawVerticalOverscanBBox() vOverscanBBox = geom.BoxI(bbox.getBegin(), geom.ExtentI(bbox.getWidth(), h - bbox.getBeginY())) amp.setRawBBox(ampExp.getBBox()) amp.setRawHorizontalOverscanBBox(hOverscanBBox) amp.setRawVerticalOverscanBBox(vOverscanBBox) # # This gets all the geometry right for the amplifier, but the size # of the untrimmed image will be wrong and we'll put the amp # sections in the wrong places, i.e. # amp.getRawXYOffset() # will be wrong. So we need to recalculate the offsets. # xRawExtent, yRawExtent = amp.getRawBBox().getDimensions() x0, y0 = amp.getRawXYOffset() ix, iy = x0//ow, y0/oh x0, y0 = ix*xRawExtent, iy*yRawExtent amp.setRawXYOffset(geom.ExtentI(ix*xRawExtent, iy*yRawExtent)) # # Check the "IRAF" keywords, but don't abort if they're wrong # # Only warn about the first amp, use debug for the others # detsec = bboxFromIraf(ampMd["DETSEC"]) if "DETSEC" in ampMd else None datasec = bboxFromIraf(ampMd["DATASEC"]) if "DATASEC" in ampMd else None biassec = bboxFromIraf(ampMd["BIASSEC"]) if "BIASSEC" in ampMd else None logCmd = logger.warn if i == 0 else logger.debug if detsec and amp.getBBox() != detsec: logCmd("DETSEC doesn't match for %s (%s != %s)", dataId, amp.getBBox(), detsec) if datasec and amp.getRawDataBBox() != datasec: logCmd("DATASEC doesn't match for %s (%s != %s)", dataId, amp.getRawDataBBox(), detsec) if biassec and amp.getRawHorizontalOverscanBBox() != biassec: logCmd("BIASSEC doesn't match for %s (%s != %s)", dataId, amp.getRawHorizontalOverscanBBox(), detsec) ampDict = {} for amp, ampExp in zip(ccd, ampExps): ampDict[amp.getName()] = ampExp exposure = assembleTask.assembleCcd(ampDict) md = componentInfo['raw_hdu'].obj fix_header(md) # No mapper so cannot specify the translator class exposure.setMetadata(md) visitInfo = LsstCamMakeRawVisitInfo(logger)(md) exposure.getInfo().setVisitInfo(visitInfo) boresight = visitInfo.getBoresightRaDec() rotangle = visitInfo.getBoresightRotAngle() if boresight.isFinite(): exposure.setWcs(getWcsFromDetector(exposure.getDetector(), boresight, 90*geom.degrees - rotangle)) else: # Should only warn for science observations but VisitInfo does not know logger.warn("Unable to set WCS for %s from header as RA/Dec/Angle are unavailable" % (dataId,)) return exposure
def subtractXTalk(mi, coeffs, minPixelToMask=45000, crosstalkStr="CROSSTALK"): """Subtract the crosstalk from MaskedImage mi given a set of coefficients The pixels affected by signal over minPixelToMask have the crosstalkStr bit set """ sctrl = afwMath.StatisticsControl() sctrl.setAndMask(mi.getMask().getPlaneBitMask("BAD")) bkgd = afwMath.makeStatistics(mi, afwMath.MEDIAN, sctrl).getValue() # # These are the pixels that are bright enough to cause crosstalk (more # precisely, the ones that we label as causing crosstalk; in reality all # pixels cause crosstalk) # tempStr = "TEMP" # mask plane used to record the bright pixels that we need to mask msk = mi.getMask() msk.addMaskPlane(tempStr) try: fs = afwDetect.FootprintSet(mi, afwDetect.Threshold(minPixelToMask), tempStr) mi.getMask().addMaskPlane(crosstalkStr) afwDisplay.getDisplay().setMaskPlaneColor(crosstalkStr, afwDisplay.MAGENTA) # the crosstalkStr bit will now be set whenever we subtract crosstalk fs.setMask(mi.getMask(), crosstalkStr) crosstalk = mi.getMask().getPlaneBitMask(crosstalkStr) width, height = mi.getDimensions() for i in range(nAmp): bbox = geom.BoxI(geom.PointI(i*(width//nAmp), 0), geom.ExtentI(width//nAmp, height)) ampI = mi.Factory(mi, bbox) for j in range(nAmp): if i == j: continue bbox = geom.BoxI(geom.PointI(j*(width//nAmp), 0), geom.ExtentI(width//nAmp, height)) if (i + j)%2 == 1: ampJ = afwMath.flipImage(mi.Factory(mi, bbox), True, False) # no need for a deep copy else: ampJ = mi.Factory(mi, bbox, afwImage.LOCAL, True) msk = ampJ.getMask() if np.all(msk.getArray() & msk.getPlaneBitMask("BAD")): # Bad amplifier; ignore it completely --- its effect will # come out in the bias continue msk &= crosstalk ampJ -= bkgd ampJ *= coeffs[j][i] ampI -= ampJ # # Clear the crosstalkStr bit in the original bright pixels, where # tempStr is set # msk = mi.getMask() temp = msk.getPlaneBitMask(tempStr) xtalk_temp = crosstalk | temp np_msk = msk.getArray() mask_indicies = np.where(np.bitwise_and(np_msk, xtalk_temp) == xtalk_temp) np_msk[mask_indicies] &= getattr(np, np_msk.dtype.name)(~crosstalk) finally: msk.removeAndClearMaskPlane(tempStr, True) # added in afw #1853
def makeBBoxFromList(ylist): """Given a list [(x0, y0), (xsize, ysize)], probably from a yaml file, return a BoxI """ (x0, y0), (xsize, ysize) = ylist return geom.BoxI(geom.PointI(x0, y0), geom.ExtentI(xsize, ysize))
def makeAmplifierList(ccd): """Construct a list of AmplifierBuilder objects """ # Much of this will need to be filled in when we know it. assert len(ccd) > 0 amp = list(ccd['amplifiers'].values())[0] rawBBox = makeBBoxFromList(amp['rawBBox']) # total in file xRawExtent, yRawExtent = rawBBox.getDimensions() readCorners = {"LL": ReadoutCorner.LL, "LR": ReadoutCorner.LR, "UL": ReadoutCorner.UL, "UR": ReadoutCorner.UR} amplifierList = [] for name, amp in sorted(ccd['amplifiers'].items(), key=lambda x: x[1]['hdu']): amplifier = Amplifier.Builder() amplifier.setName(name) ix, iy = amp['ixy'] perAmpData = amp['perAmpData'] if perAmpData: x0, y0 = 0, 0 # origin of data within each amp image else: x0, y0 = ix*xRawExtent, iy*yRawExtent rawDataBBox = makeBBoxFromList(amp['rawDataBBox']) # Photosensitive area xDataExtent, yDataExtent = rawDataBBox.getDimensions() amplifier.setBBox(geom.BoxI( geom.PointI(ix*xDataExtent, iy*yDataExtent), rawDataBBox.getDimensions())) rawBBox = makeBBoxFromList(amp['rawBBox']) rawBBox.shift(geom.ExtentI(x0, y0)) amplifier.setRawBBox(rawBBox) rawDataBBox = makeBBoxFromList(amp['rawDataBBox']) rawDataBBox.shift(geom.ExtentI(x0, y0)) amplifier.setRawDataBBox(rawDataBBox) rawSerialOverscanBBox = makeBBoxFromList(amp['rawSerialOverscanBBox']) rawSerialOverscanBBox.shift(geom.ExtentI(x0, y0)) amplifier.setRawHorizontalOverscanBBox(rawSerialOverscanBBox) rawParallelOverscanBBox = makeBBoxFromList(amp['rawParallelOverscanBBox']) rawParallelOverscanBBox.shift(geom.ExtentI(x0, y0)) amplifier.setRawVerticalOverscanBBox(rawParallelOverscanBBox) rawSerialPrescanBBox = makeBBoxFromList(amp['rawSerialPrescanBBox']) rawSerialPrescanBBox.shift(geom.ExtentI(x0, y0)) amplifier.setRawPrescanBBox(rawSerialPrescanBBox) if perAmpData: amplifier.setRawXYOffset(geom.Extent2I(ix*xRawExtent, iy*yRawExtent)) else: amplifier.setRawXYOffset(geom.Extent2I(0, 0)) amplifier.setReadoutCorner(readCorners[amp['readCorner']]) amplifier.setGain(amp['gain']) amplifier.setReadNoise(amp['readNoise']) amplifier.setSaturation(amp['saturation']) amplifier.setSuspectLevel(amp.get('suspect', np.nan)) # flip data when assembling if needs be (e.g. data from the serial at the top of a CCD) flipX, flipY = amp.get("flipXY") amplifier.setRawFlipX(flipX) amplifier.setRawFlipY(flipY) # linearity placeholder stuff amplifier.setLinearityCoeffs([float(val) for val in amp['linearityCoeffs']]) amplifier.setLinearityType(amp['linearityType']) amplifier.setLinearityThreshold(float(amp['linearityThreshold'])) amplifier.setLinearityMaximum(float(amp['linearityMax'])) amplifier.setLinearityUnits("DN") amplifierList.append(amplifier) return amplifierList