def bypass_instcal(self, datasetType, pythonType, butlerLocation, dataId): # Workaround until I can access the butler instcalMap = self.map_instcal(dataId) dqmaskMap = self.map_dqmask(dataId) wtmapMap = self.map_wtmap(dataId) instcalType = getattr(afwImage, instcalMap.getPythonType().split(".")[-1]) dqmaskType = getattr(afwImage, dqmaskMap.getPythonType().split(".")[-1]) wtmapType = getattr(afwImage, wtmapMap.getPythonType().split(".")[-1]) instcal = instcalType(instcalMap.getLocationsWithRoot()[0]) dqmask = dqmaskType(dqmaskMap.getLocationsWithRoot()[0]) wtmap = wtmapType(wtmapMap.getLocationsWithRoot()[0]) mask = self.translate_dqmask(dqmask) variance = self.translate_wtmap(wtmap) mi = afwImage.MaskedImageF(afwImage.ImageF(instcal.getImage()), mask, variance) md = readMetadata(instcalMap.getLocationsWithRoot()[0]) fix_header(md, translator_class=DecamTranslator) wcs = makeSkyWcs(md, strip=True) exp = afwImage.ExposureF(mi, wcs) exp.setPhotoCalib( afwImage.makePhotoCalibFromCalibZeroPoint( 10**(0.4 * md.getScalar("MAGZERO")), 0)) visitInfo = self.makeRawVisitInfo(md=md) exp.getInfo().setVisitInfo(visitInfo) for kw in ('LTV1', 'LTV2'): md.remove(kw) exp.setMetadata(md) return exp
def readMetadata(self): """Read all header metadata directly into a PropertyList. Returns ------- metadata : `~lsst.daf.base.PropertyList` Header metadata. """ md = readMetadata(self.fileDescriptor.location.path) fix_header(md) return md
def std_dark(self, item, dataId): exp = afwImage.makeExposure(afwImage.makeMaskedImage(item)) rawPath = self.map_raw(dataId).getLocations()[0] headerPath = re.sub(r'[\[](\d+)[\]]$', "[0]", rawPath) md0 = readMetadata(headerPath) fix_header(md0, translator_class=DecamTranslator) visitInfo = self.makeRawVisitInfo(md0) exp.getInfo().setVisitInfo(visitInfo) return self._standardizeExposure(self.calibrations["dark"], exp, dataId, filter=False)
def bypass_raw_visitInfo(self, datasetType, pythonType, location, dataId): fileName = location.getLocationsWithRoot()[0] mat = re.search(r"\[(\d+)\]$", fileName) if mat: hdu = int(mat.group(1)) md = readMetadata(fileName, hdu=hdu) else: md = readMetadata(fileName) # or hdu = INT_MIN; -(1 << 31) makeVisitInfo = self.MakeRawVisitInfoClass(log=self.log) fix_header(md, translator_class=self.translatorClass) return makeVisitInfo(md)
def test_readMetadata(self): dataId = {'detector': 25} # detector 25 is in HDU 1 expected = lsst.afw.fits.readMetadata(self.filename, 1) self.assertEqual(expected['CCDNUM'], 25) # sanity check self.check_readMetadata(dataId, expected) dataId = {'detector': 1} # detector 1 is in HDU 2 expected = lsst.afw.fits.readMetadata(self.filename, 2) astro_metadata_translator.fix_header(expected) self.assertEqual(expected['CCDNUM'], 1) # sanity check self.check_readMetadata(dataId, expected)
def _listdir(self, path, prefix): for file in os.listdir(path): fileName = os.path.join(path, file) md = readMetadata(fileName) fix_header(md, translator_class=self._translatorClass) if "EXPNUM" not in md: return expnum = md["EXPNUM"] if expnum not in self.expnumMapper: self.expnumMapper[expnum] = { self.instcalPrefix: None, self.wtmapPrefix: None, self.dqmaskPrefix: None } self.expnumMapper[expnum][prefix] = fileName
def test_translator_fix_header(self): """Check that translator classes can fix headers.""" # Read in a known header header = read_test_file("fitsheader-decam-0160496.yaml", dir=os.path.join(TESTDIR, "data")) self.assertEqual(header["DTSITE"], "ct") fixed = fix_header(header, translator_class=NotDecamTranslator) self.assertTrue(fixed) self.assertEqual(header["DTSITE"], "hi") header["DTSITE"] = "reset" with self.assertLogs("astro_metadata_translator", level="FATAL"): fixed = fix_header(header, translator_class=AlsoNotDecamTranslator) self.assertFalse(fixed) self.assertEqual(header["DTSITE"], "reset")
def _standardizeCpMasterCal(self, datasetType, item, dataId, setFilter=False): """Standardize a MasterCal image obtained from NOAO archive into Exposure These MasterCal images are MEF files with one HDU for each detector. Some WCS header, eg CTYPE1, exists only in the zeroth extensionr, so info in the zeroth header need to be copied over to metadata. Parameters ---------- datasetType : `str` Dataset type ("bias", "flat", or "illumcor"). item : `lsst.afw.image.DecoratedImage` The image read by the butler. dataId : data ID Data identifier. setFilter : `bool` Whether to set the filter in the Exposure. Returns ------- result : `lsst.afw.image.Exposure` The standardized Exposure. """ mi = afwImage.makeMaskedImage(item.getImage()) md = item.getMetadata() masterCalMap = getattr(self, "map_" + datasetType) masterCalPath = masterCalMap(dataId).getLocationsWithRoot()[0] headerPath = re.sub(r'[\[](\d+)[\]]$', "[0]", masterCalPath) md0 = readMetadata(headerPath) fix_header(md0, translator_class=DecamTranslator) for kw in ('CTYPE1', 'CTYPE2', 'CRVAL1', 'CRVAL2', 'CUNIT1', 'CUNIT2', 'CD1_1', 'CD1_2', 'CD2_1', 'CD2_2'): if kw in md0.paramNames() and kw not in md.paramNames(): md.add(kw, md0.getScalar(kw)) wcs = makeSkyWcs(md, strip=True) exp = afwImage.makeExposure(mi, wcs) exp.setMetadata(md) return self._standardizeExposure(self.calibrations[datasetType], exp, dataId, filter=setFilter)
def getInfo(self, filename): """Get information about the image from the filename and its contents Here, we open the image and parse the header, but one could also look at the filename itself and derive information from that, or set values from the configuration. Parameters ---------- filename : `str` Name of file to inspect Returns ------- phuInfo : `dict` File properties infoList : `list` List of file properties for each extension """ md = readMetadata(filename, self.config.hdu) fix_header(md, translator_class=self.translator_class) phuInfo = self.getInfoFromMetadata(md) if len(self.config.extnames) == 0: # No extensions to worry about return phuInfo, [phuInfo] # Look in the provided extensions extnames = set(self.config.extnames) extnum = 0 infoList = [] while len(extnames) > 0: extnum += 1 try: md = readMetadata(filename, extnum) fix_header(md, translator_class=self.translator_class) except Exception as e: self.log.warning("Error reading %s extensions %s: %s", filename, extnames, e) break ext = self.getExtensionName(md) if ext in extnames: hduInfo = self.getInfoFromMetadata(md, info=phuInfo.copy()) # We need the HDU number when registering MEF files. hduInfo["hdu"] = extnum infoList.append(hduInfo) extnames.discard(ext) return phuInfo, infoList
def std_raw(self, item, dataId): """Fixup raw image problems. 1. Apply header patches. This should be done by the CameraMapper base class, but it isn't yet (DM-23959). 2. Fix an early ADC bug. Since this is almost certainly an FPGA bug, I'll base the decision on the FPGA version number. As of 2016-12-01 the keyword is misnamed, so we can fix the format if the keyword does not exist. See _shiftAmpPixels() for the implementation. Parameters ---------- item : image-like object Can be any of lsst.afw.image.Exposure, lsst.afw.image.DecoratedImage, lsst.afw.image.Image or lsst.afw.image.MaskedImage the image-like object whose header information needs to be patched. dataId : `dict` Dataset identifier Returns ------- item : image-like object the input object with patched header information. """ exp = super(PfsMapper, self).std_raw(item, dataId) md = exp.getMetadata() fix_header(md, translator_class=PfsTranslator) try: dataVersion = int(md.get('W_VERSIONS_FPGA'), 16) except Exception: dataVersion = None if dataVersion is not None and dataVersion <= 0x0070: self._shiftAmpPixels(exp) return exp
def std_raw_md(self, item, dataId): """Fixup raw header metadata problems. This should be done by the CameraMapper base class, but it isn't yet (DM-23959). Parameters ---------- item : `lsst.daf.base.PropertyList` The raw metadata to be fixed dataId : `dict` Dataset identifier Returns ------- item : `lsst.daf.base.PropertyList` The modified raw metadata. """ fix_header(item, translator_class=PfsTranslator) return item
def readRawFitsHeader(fileName, translator_class=None): """Read a FITS header from a raw file and fix it up as required. Parameters ---------- fileName : `str` Name of the FITS file. Can include a HDU specifier (although 0 is ignored). translator_class : `~astro_metadata_translator.MetadataTranslator`, optional Any translator class to use for fixing up the header. Returns ------- md : `PropertyList` Metadata from file. We also merge the contents with the next HDU if an ``INHERIT`` key is not specified. If an explicit HDU is encoded with the file name and it is greater than 0 then no merging will occur. """ mat = re.search(r"\[(\d+)\]$", fileName) hdu = None if mat: # Treat 0 as a special case # For some instruments the primary header is empty requested = int(mat.group(1)) if requested > 0: hdu = requested if hdu is not None: md = lsst.afw.fits.readMetadata(fileName, hdu=hdu) else: # For raw some of these files need the second header to be # read as well. Not all instruments want the double read # but for now it's easiest to always merge. phdu = lsst.afw.fits.readMetadata(fileName, 0) md = lsst.afw.fits.readMetadata(fileName) md = merge_headers([phdu, md], mode="overwrite") fix_header(md, translator_class=translator_class) return md
def readMetadata(self): """Read all header metadata directly into a PropertyList. Specialist version since some of our data does not set INHERIT=T so we have to merge the headers manually. Returns ------- metadata : `~lsst.daf.base.PropertyList` Header metadata. """ file = self.fileDescriptor.location.path phdu = lsst.afw.fits.readMetadata(file, 0) if "INHERIT" in phdu: # Trust the inheritance flag return super().readMetadata() # Merge ourselves md = merge_headers([phdu, lsst.afw.fits.readMetadata(file)], mode="overwrite") fix_header(md) return md
def readMetadata(self): """Read all header metadata directly into a PropertyList. Specialist version since some of our data does not set INHERIT=T so we have to merge the headers manually. Returns ------- metadata : `~lsst.daf.base.PropertyList` Header metadata. """ file = self.fileDescriptor.location.path phdu = lsst.afw.fits.readMetadata(file, 0) index, md = self._determineHDU(self.dataId['detector']) if "INHERIT" in phdu: # Trust the inheritance flag return super().readMetadata() # Merge ourselves md = merge_headers([phdu, md], mode="overwrite") #fix_header(md) astro_metadata_translator.fix_header(md,translator_class=VircamTranslator) #print('md:',md) return md
def test_basic_fix_header(self): """Test that a header can be fixed if we specify a local path. """ header = read_test_file("fitsheader-decam-0160496.yaml", dir=os.path.join(TESTDIR, "data")) self.assertEqual(header["DETECTOR"], "S3-111_107419-8-3") # First fix header but using no search path (should work as no-op) fixed = fix_header(header) self.assertFalse(fixed) # Now using the test corrections directory fixed = fix_header(header, search_path=os.path.join(TESTDIR, "data", "corrections")) self.assertTrue(fixed) self.assertEqual(header["DETECTOR"], "NEW-ID") # Test that fix_header of unknown header is allowed header = {"SOMETHING": "UNKNOWN"} fixed = fix_header(header) self.assertFalse(fixed)
def extractMetadata(self, filename: str) -> RawFileData: datasets = [] fitsData = lsst.afw.fits.Fits(filename, "r") # NOTE: The primary header (HDU=0) does not contain detector data. for i in range(1, fitsData.countHdus()): fitsData.setHdu(i) header = fitsData.readMetadata() if not header["EXTNAME"].startswith("ccd"): continue fix_header(header) datasets.append(self._calculate_dataset_info(header, filename)) # The data model currently assumes that whilst multiple datasets # can be associated with a single file, they must all share the # same formatter. instrument = MegaPrime() FormatterClass = instrument.getRawFormatter(datasets[0].dataId) self.log.info(f"Found images for {len(datasets)} detectors in {filename}") return RawFileData(datasets=datasets, filename=filename, FormatterClass=FormatterClass, instrumentClass=type(instrument))
def test_hsc_fix_header(self): """Check that one of the known HSC corrections is being applied properly.""" header = { "EXP-ID": "HSCA00120800", "INSTRUME": "HSC", "DATA-TYP": "FLAT" } fixed = fix_header(header, translator_class=HscTranslator) self.assertTrue(fixed) self.assertEqual(header["DATA-TYP"], "OBJECT") # And that this header won't be corrected header = { "EXP-ID": "HSCA00120800X", "INSTRUME": "HSC", "DATA-TYP": "FLAT" } fixed = fix_header(header, translator_class=HscTranslator) self.assertFalse(fixed) self.assertEqual(header["DATA-TYP"], "FLAT")
def extractMetadata(self, filename: str) -> RawFileData: """Extract and process metadata from a single raw file. Parameters ---------- filename : `str` Path to the file. Returns ------- data : `RawFileData` A structure containing the metadata extracted from the file, as well as the original filename. All fields will be populated, but the `RawFileData.dataId` attribute will be a minimal (unexpanded) `DataCoordinate` instance. Notes ----- Assumes that there is a single dataset associated with the given file. Instruments using a single file to store multiple datasets must implement their own version of this method. """ # Manually merge the primary and "first data" headers here because we # do not know in general if an input file has set INHERIT=T. phdu = readMetadata(filename, 0) header = merge_headers([phdu, readMetadata(filename)], mode="overwrite") fix_header(header) datasets = [self._calculate_dataset_info(header, filename)] # The data model currently assumes that whilst multiple datasets # can be associated with a single file, they must all share the # same formatter. FormatterClass = self.instrument.getRawFormatter(datasets[0].dataId) return RawFileData(datasets=datasets, filename=filename, FormatterClass=FormatterClass)
def test_fix_header(self): from astro_metadata_translator import fix_header from astro_metadata_translator.tests import read_test_file # Test that header fix up is working # Not all headers are used in metadata translation test_data = ( ("latiss-AT_O_20210212_000006.yaml", dict(RASTART=260.024385071917)), ("latiss-AT_O_20210210_000011.yaml", dict(RASTART=355.41750341182313)), ) for filename, expected in test_data: with self.subTest(f"Testing {filename}"): header = read_test_file(filename, dir=self.datadir) modified = fix_header(header) self.assertTrue(modified) for k, v in expected.items(): self.assertEqual(header[k], v, f"Testing {k} in {filename}")
def readMetadata(self): index, metadata = self._determineHDU(self.dataId['detector']) astro_metadata_translator.fix_header(metadata) return metadata
def getInfo(self, filename): """Get information about the image from the filename and its contents The information we want isn't in the headers, but in the filname. Parameters ---------- filename : `str` Name of file to inspect Returns ------- info : `dict` File properties from the PHU. infoList : `list` of `dict` File properties from the extensions. """ self.log.debug('interpreting filename <%s>' % filename) path, name = os.path.split(filename) matches = re.search( r"^PF(%s)(%s)-?(\d{6})(\d)(\d)\.fits$" % (self.sites, self.categories), name) if not matches: raise RuntimeError("Unable to interpret filename: %s" % filename) site, category, visit, spectrograph, armNum = matches.groups() armNum = int(armNum) spectrograph = int(spectrograph) visit = int(visit, base=10) # spectrograph 2 was called 9 at JHU, at least in the early days, so if we find # a spectrograph 9 we re-assign its number to 2 if spectrograph == 9: spectrograph = 2 self.log.info("Visit %06d has spectrograph == 9" % visit) if spectrograph < 1 or spectrograph > self.nSpectrograph + 1: raise RuntimeError('spectrograph (=%d) out of bounds 1..%d' % (spectrograph, self.nSpectrograph)) try: arm = self.arms[armNum - 1] # 1-indexed except IndexError as exc: raise IndexError('armNum=%d out of bounds 1..%d' % (armNum, max(self.arms.keys()))) from exc ccd = PfsMapper.computeDetectorId(spectrograph, arm) self.log.debug( 'site = <%s>, category = <%s>, visit = <%s>, spectrograph = <%d>, armNum = <%d>, ' 'arm = <%s>, ccd = <%d>' % (site, category, visit, spectrograph, armNum, arm, ccd)) info = dict(site=site, category=category, visit=visit, filter=arm, arm=arm, spectrograph=spectrograph, ccd=ccd) if os.path.exists(filename): header = afwImage.readMetadata(filename) # Patch the header original = header.toOrderedDict() fixed = header.toOrderedDict() fix_header(fixed, translator_class=PfsTranslator, filename=filename) for kk, vv in fixed.items(): if kk in original and vv == original[kk]: continue header.set(kk, vv) info = self.getInfoFromMetadata(header, info=info) return info, [info]
def read_file(file, hdrnum, print_trace, outstream=sys.stdout, errstream=sys.stderr, output_mode="verbose", write_heading=False): """Read the specified file and process it. Parameters ---------- file : `str` The file from which the header is to be read. hdrnum : `int` The HDU number to read. The primary header is always read and merged with the header from this HDU. print_trace : `bool` If there is an error reading the file and this parameter is `True`, a full traceback of the exception will be reported. If `False` prints a one line summary of the error condition. outstream : `io.StringIO`, optional Output stream to use for standard messages. Defaults to `sys.stdout`. errstream : `io.StringIO`, optional Stream to send messages that would normally be sent to standard error. Defaults to `sys.stderr`. output_mode : `str`, optional Output mode to use. Must be one of "verbose", "none", "table", "yaml", or "fixed". "yaml" and "fixed" can be modified with a "native" suffix to indicate that the output should be a representation of the native object type representing the header (which can be PropertyList or an Astropy header). Without this modify headers will be dumped as simple `dict` form. "auto" is not allowed by this point. write_heading: `bool`, optional If `True` and in table mode, write a table heading out before writing the content. Returns ------- success : `bool` `True` if the file was handled successfully, `False` if the file could not be processed. """ if output_mode not in OUTPUT_MODES: raise ValueError(f"Output mode of '{output_mode}' is not understood.") if output_mode == "auto": raise ValueError("Output mode can not be 'auto' here.") # This gets in the way in tabular mode if output_mode != "table": print(f"Analyzing {file}...", file=errstream) try: if file.endswith(".yaml"): md = read_test_file(file, ) if hdrnum != 0: # YAML can't have HDUs hdrnum = 0 else: md = read_metadata(file, 0) if md is None: print(f"Unable to open file {file}", file=errstream) return False if hdrnum != 0: mdn = read_metadata(file, int(hdrnum)) # Astropy does not allow append mode since it does not # convert lists to multiple cards. Overwrite for now if mdn is not None: md = merge_headers([md, mdn], mode="overwrite") else: print(f"HDU {hdrnum} was not found. Ignoring request.", file=errstream) if output_mode.endswith("native"): # Strip native and don't change type of md output_mode = output_mode[:-len("native")] else: # Rewrite md as simple dict for output md = {k: v for k, v in md.items()} if output_mode in ("yaml", "fixed"): if output_mode == "fixed": fix_header(md, filename=file) # The header should be written out in the insertion order print(yaml.dump(md, sort_keys=False), file=outstream) return True obs_info = ObservationInfo(md, pedantic=True, filename=file) if output_mode == "table": columns = [ "{:{fmt}}".format(getattr(obs_info, c["attr"]), fmt=c["format"]) for c in TABLE_COLUMNS ] if write_heading: # Construct headings of the same width as the items # we have calculated. Doing this means we don't have to # work out for ourselves how many characters will be used # for non-strings (especially Quantity) headings = [] separators = [] for thiscol, defn in zip(columns, TABLE_COLUMNS): width = len(thiscol) headings.append("{:{w}.{w}}".format(defn["label"], w=width)) separators.append("-" * width) print(" ".join(headings), file=outstream) print(" ".join(separators), file=outstream) row = " ".join(columns) print(row, file=outstream) elif output_mode == "verbose": print(f"{obs_info}", file=outstream) elif output_mode == "none": pass else: raise RuntimeError( f"Output mode of '{output_mode}' not recognized but should be known." ) except Exception as e: if print_trace: traceback.print_exc(file=outstream) else: print(repr(e), file=outstream) return False return True
def bypass_raw(self, datasetType, pythonType, location, dataId): """Magic method that is called automatically if it exists. """ from lsst.ip.isr import AssembleCcdTask config = AssembleCcdTask.ConfigClass() config.doTrim = False assembleTask = AssembleCcdTask(config=config) logger = lsst.log.Log.getLogger("ZtfMapper") # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= assert len(location.getLocations() ) == 1 # KTL says that this is always true, but check relativeFileName = location.getLocations()[0] if location.storage.exists(relativeFileName): fileName = location.storage.locationWithRoot(relativeFileName) data = afwImage.ImageF(fileName, hdu=1) data = pythonType(afwImage.MaskedImageF(data)) else: raise IOError(f"Unable to find {relativeFileName}") # Get the Detector detector = self.camera[self._extractDetectorName(dataId)] # # Read all the data # ampDict = {} for i, amp in enumerate(detector, 1): ampExp = pythonType(amp.getRawBBox()) ampExp.setDetector(detector) ampDict[amp.getName()] = ampExp hdu = amp.get('hdu') if i == 1: assert i == hdu # don't read twice data = data.image else: data = afwImage.ImageF(fileName, hdu=hdu) ampExp[amp.getRawDataBBox()].image = data bias = afwImage.ImageF(fileName, hdu=hdu + 4) ampExp[amp.getRawHorizontalOverscanBBox()].image = bias exposure = assembleTask.assembleCcd(ampDict) md = afwImage.readMetadata(fileName, hdu=0) fix_header(md, translator_class=self.translatorClass) exposure.setMetadata(md) visitInfo = ZtfMakeRawVisitInfo(logger)(md) exposure.getInfo().setVisitInfo(visitInfo) boresight = visitInfo.getBoresightRaDec() if boresight.isFinite(): exposure.setWcs( getWcsFromDetector(exposure.getDetector(), boresight, 180 * geom.degrees, flipX=True)) else: logger.warn( f"Unable to set WCS for {dataId} from header as RA/Dec/Angle are unavailable" ) return exposure
def readMetadata(self): # Headers are duplicated so no need to merge with primary index, metadata = self._determineHDU(self.dataId["detector"]) fix_header(metadata) return metadata
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