Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-18739: Generate defects from standardized format #66

Merged
merged 15 commits into from
Jun 21, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion SConstruct
@@ -1,3 +1,3 @@
# -*- python -*-
from lsst.sconsUtils import scripts
scripts.BasicSConstruct("obs_test")
scripts.BasicSConstruct("obs_test", defaultTargets=scripts.DEFAULT_TARGETS + ("data",))
2 changes: 1 addition & 1 deletion config/ingestCalibs.py
Expand Up @@ -10,6 +10,6 @@
}
config.register.detector = ['filter']
config.register.visit = ['calibDate']
config.register.tables = ['bias', 'flat'] # defects are hardcoded into mapper; cannot be ingested
config.register.tables = ['bias', 'flat', 'defects']
config.register.validityUntilSuperseded = config.register.tables
config.register.unique = ['filter', 'calibDate']
14 changes: 14 additions & 0 deletions config/ingestDefects.py
@@ -0,0 +1,14 @@
# Config override for lsst.pipe.tasks.IngestCalibsTask
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean IngestDefectsTask?

config.parse.translation = {'calibDate': 'CALIBDATE',
'filter': 'FILTER',
'ccd': 'DETECTOR',
}
config.register.columns = {'calibDate': 'text',
'validStart': 'text',
'validEnd': 'text',
'filter': 'text',
'ccd': 'int',
}
config.register.visit = ['calibDate']
config.register.validityUntilSuperseded = ['defects',]
config.register.unique = ['calibDate']
6 changes: 5 additions & 1 deletion data/ReadMe.md
Expand Up @@ -55,7 +55,11 @@ To make an obs_test defects file from the bias frame generated above, from the o

setup -r .
data/utils/defectsFromBias.py data/input/bias/bias.fits.gz
mv defects_c0.fits data/input/defects/
mv defects_c0.ecsv $OBS_TEST_DATA_DIR/test/defects/0/19700101T000000.ecsv

The defects are persisted in the ingested for because ingestion depends on pipe_tasks which introduces a circular dependency.
r-owen marked this conversation as resolved.
Show resolved Hide resolved

ingestDefects.py data/input $OBS_TEST_DATA_DIR/test/defects --calib data/inpu

## Hints for using the data

Expand Down
Binary file added data/input/calibRegistry.sqlite3
Binary file not shown.
Binary file modified data/input/defects/defects.fits
100755 → 100644
Binary file not shown.
93 changes: 17 additions & 76 deletions data/utils/defectsFromBias.py
Expand Up @@ -21,86 +21,20 @@
# see <http://www.lsstcorp.org/LegalNotices/>.
#
import argparse
import time

import numpy
from astropy.io import fits
from dateutil import parser as date_parser
r-owen marked this conversation as resolved.
Show resolved Hide resolved

import lsst.afw.geom as afwGeom
import lsst.afw.image as afwImage
from lsst.ip.isr import getDefectListFromMask
from lsst.meas.algorithms import Defects

DefectsPath = "defects_c0.fits"
DefectsPath = "defects_c0"
"""Output path for defects file."""
detectorName = "0"
"""Detector name."""
detectorSerial = "0000011"
"""Detector serial code"""


def getBBoxList(path, detectorName):
"""Read a defects file and return the defects as a list of bounding
boxes.

Parameters
---------
path : `str`
Path to input defects file; a fits file.
detectorName : `str`
Name of detector.
"""
with fits.open(path) as hduList:
for hdu in hduList[1:]:
if hdu.header["name"] != detectorName:
print("skipping header with name=%r" % (hdu.header["name"],))
continue

bboxList = []
for data in hdu.data:
bbox = afwGeom.Box2I(
afwGeom.Point2I(int(data['x0']), int(data['y0'])),
afwGeom.Extent2I(int(data['width']), int(data['height'])),
)
bboxList.append(bbox)
return bboxList
raise RuntimeError("Data not found for detector %r" % (detectorName,))


def writeDefectsFile(bboxList, path, detectorSerial, detectorName):
"""Write a defects FITS file.

Parameters
----------
bboxList : `list` of `lsst.geom.Box2I`
List of bounding boxes defining defect locations.
path : `str`
Path of output defects file; should end with ".fits".
detectorSerial : `str`
Serial code of detector.
detectorName : `str`
Name of detector.
"""
head = fits.Header()
head.update('SERIAL', detectorSerial, 'Serial of the detector')
head.update('NAME', detectorName, 'Name of detector for this defect map')
head.update('CDATE', time.asctime(time.gmtime()), 'UTC of creation')

x0 = numpy.array([d.getMinX() for d in bboxList])
y0 = numpy.array([d.getMinY() for d in bboxList])
width = numpy.array([d.getWidth() for d in bboxList])
height = numpy.array([d.getHeight() for d in bboxList])

col1 = fits.Column(name='x0', format='I', array=numpy.array(x0))
col2 = fits.Column(name='y0', format='I', array=numpy.array(y0))
col3 = fits.Column(name='height', format='I', array=numpy.array(height))
col4 = fits.Column(name='width', format='I', array=numpy.array(width))
cols = fits.ColDefs([col1, col2, col3, col4])
tbhdu = fits.new_table(cols, header=head)
hdu = fits.PrimaryHDU()
thdulist = fits.HDUList([hdu, tbhdu])
thdulist.writeto(DefectsPath)


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="""Construct a defects file from the mask plane of a test camera bias frame.
Expand All @@ -112,13 +46,20 @@ def writeDefectsFile(bboxList, path, detectorSerial, detectorName):
args = parser.parse_args()

biasMI = afwImage.MaskedImageF(args.bias)
defectList = getDefectListFromMask(biasMI, "BAD")
bboxList = [defect.getBBox() for defect in defectList]
writeDefectsFile(bboxList, DefectsPath, detectorSerial, detectorName)
defectList = Defects.fromMask(biasMI, "BAD")
SimonKrughoff marked this conversation as resolved.
Show resolved Hide resolved
valid_start = date_parser.parse('19700101T000000')
md = defectList.getMetadata()
md['INSTRUME'] = 'test'
md['DETECTOR'] = detectorName
md['CALIBDATE'] = valid_start.isoformat()
md['FILTER'] = None
md['CALIB_ID'] = (f'detector={detectorName} calibDate={valid_start.isoformat()} '
'ccd={detectorName} ccdnum={detectorName} filter=None')
SimonKrughoff marked this conversation as resolved.
Show resolved Hide resolved
defectList.writeText(DefectsPath)
print("wrote defects file %r" % (DefectsPath,))

test2BBoxList = getBBoxList(DefectsPath, detectorName)
assert len(bboxList) == len(test2BBoxList)
for boxA, boxB in zip(bboxList, test2BBoxList):
assert boxA == boxB
test2defectList = Defects.readText(DefectsPath+".ecsv")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it work to include the ".ecsv" suffix on DefectsPath itself -- would the correct file get written? If so, I think it would make the code a bit simpler and easier to understand. Also the description string would give the correct path for the output file (right now it is missing the ".ecsv" suffix).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want to get the return value from writeText and use that directly in readText (writeText forces the file extension based on the type of text file it is writing). The debug print message should also use the actual name used as returned by writeText.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds reasonable, though it means that the full name of the file cannot be included in the help string for the command line parser is produced. I suppose one could always print a message when saving the data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I have addressed this.

assert len(defectList) == len(test2defectList)
SimonKrughoff marked this conversation as resolved.
Show resolved Hide resolved
for dA, dB in zip(defectList, test2defectList):
SimonKrughoff marked this conversation as resolved.
Show resolved Hide resolved
assert dA.getBBox() == dB.getBBox()
print("verified that defects file %r round trips correctly" % (DefectsPath,))
9 changes: 7 additions & 2 deletions policy/testMapper.yaml
@@ -1,5 +1,3 @@
defects: ../description/defects

needCalibRegistry: false

levels:
Expand Down Expand Up @@ -48,6 +46,13 @@ exposures:
template: fcr/v%(visit)d_f%(filter)s.fcr.fits

calibrations:
defects:
level: Ccd
persistable: BaseCatalog
python: lsst.meas.algorithms.Defects
storage: FitsCatalogStorage
tables: defects
template: defects/defects.fits
bias:
level: Ccd
persistable: ExposureF
Expand Down
1 change: 1 addition & 0 deletions ups/obs_test.table
Expand Up @@ -5,6 +5,7 @@ setupRequired(pex_config)
setupRequired(pex_policy)
setupRequired(numpy)
setupRequired(utils)
setupRequired(obs_test_data)
setupOptional(astropy)

envPrepend(PYTHONPATH, ${PRODUCT_DIR}/python)