Skip to content

Commit

Permalink
Merge pull request #602 from lsst/tickets/DM-48933
Browse files Browse the repository at this point in the history
DM-48933: Fix code transforming between focal plane position and Amp pixels
  • Loading branch information
aaronroodman authored Mar 3, 2025
2 parents 21c7c11 + d8e2a8d commit 5b30495
Showing 1 changed file with 68 additions and 103 deletions.
171 changes: 68 additions & 103 deletions python/lsst/obs/lsst/cameraTransforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import lsst.geom as geom
import lsst.afw.cameraGeom as cameraGeom

__all__ = ["LsstCameraTransforms", "channelToAmp"]
__all__ = ["LsstCameraTransforms"]


class LsstCameraTransforms():
Expand Down Expand Up @@ -79,60 +79,64 @@ def getDetector(self, detectorName=None):

return self.camera[detectorName]

def ampPixelToCcdPixel(self, ampX, ampY, channel, detectorName=None):
r"""Given raw amplifier position return position on full detector.
def ampPixelToCcdPixel(self, ampX, ampY, ampName, detectorName=None):
r"""Given amplifier coordinate pixel position return pixel
position on full detector.
Parameters
----------
ampX : `int`
ampX : `float`
column on amp segment.
ampY : `int`
ampY : `float`
row on amp segment.
channel : `int`
Channel number of amplifier (1-indexed; identical to HDU).
ampName : `str`
Name of amplifier.
detectorName : `str`
Name of detector (or default from setDetectorName() if None).
Notes
-----
N.b. all pixel coordinates have the centre of the bottom-left pixel
at (0.0, 0.0).
at (0.0, 0.0). Also, both amplifier and full detector pixel
coordinates are trimmed (overscan-free).
Returns
-------
ccdX : `int`
ccdX : `float`
The column pixel position relative to the corner of the detector.
ccdY : `int`
ccdY : `float`
The row pixel position relative to the corner of the detector.
"""

return ampPixelToCcdPixel(ampX, ampY, self.getDetector(detectorName), channel)
return ampPixelToCcdPixel(ampX, ampY, self.getDetector(detectorName), ampName)

def ccdPixelToAmpPixel(self, ccdX, ccdY, detectorName=None):
r"""Given position within a detector, return the amplifier position.
r"""Given pixel position within a detector, return the amplifier
pixel position.
Parameters
----------
ccdX : `int`
ccdX : `float`
Column pixel position within detector.
ccdY : `int`
ccdY : `float`
Row pixel position within detector.
detectorName : `str`
Name of detector (or default from setDetectorName() if None).
Notes
-----
N.b. all pixel coordinates have the centre of the bottom-left pixel
at (0.0, 0.0).
at (0.0, 0.0). Also, both amplifier and full detector pixel
coordinates are trimmed (overscan-free).
Returns
-------
channel: `int`
Channel number of amplifier (1-indexed; identical to HDU).
ampX : `int`
ampName: `str`
Amplifier name, eg. 'C10'
ampX : `float`
The column coordinate relative to the corner of the single-amp
image.
ampY : `int`
ampY : `float`
The row coordinate relative to the corner of the single-amp image.
Raises
Expand All @@ -144,16 +148,16 @@ def ccdPixelToAmpPixel(self, ccdX, ccdY, detectorName=None):
amp, ampXY = ccdPixelToAmpPixel(geom.PointD(ccdX, ccdY), self.getDetector(detectorName))

ampX, ampY = ampXY
return ampToChannel(amp), ampX, ampY
return amp.getName(), ampX, ampY

def ccdPixelToFocalMm(self, ccdX, ccdY, detectorName=None):
r"""Given position within a detector return the focal plane position.
Parameters
----------
ccdX : `int`
ccdX : `float`
column pixel position within detector.
ccdY : `int`
ccdY : `float`
row pixel position within detector.
detectorName : `str`
Name of detector (or default from setDetectorName() if None).
Expand All @@ -177,15 +181,15 @@ def ccdPixelToFocalMm(self, ccdX, ccdY, detectorName=None):

return detector.transform(geom.Point2D(ccdX, ccdY), cameraGeom.PIXELS, cameraGeom.FOCAL_PLANE)

def ampPixelToFocalMm(self, ampX, ampY, channel, detectorName=None):
def ampPixelToFocalMm(self, ampX, ampY, ampName, detectorName=None):
r"""Given position within an amplifier return the focal plane position.
ampX : `int`
ampX : `float`
column on amp segment.
ampY : `int`
ampY : `float`
row on amp segment.
channel: `int`
Channel number of amplifier (1-indexed; identical to HDU).
ampName: `int`
Name of amplifier.
detectorName : `str`
Name of detector (or default from setDetectorName() if None).
Expand All @@ -212,7 +216,7 @@ def ampPixelToFocalMm(self, ampX, ampY, channel, detectorName=None):

detector = self.getDetector(detectorName)

ccdX, ccdY = ampPixelToCcdPixel(ampX, ampY, detector, channel)
ccdX, ccdY = ampPixelToCcdPixel(ampX, ampY, detector, ampName)

return detector.transform(geom.Point2D(ccdX, ccdY), cameraGeom.PIXELS, cameraGeom.FOCAL_PLANE)

Expand All @@ -238,9 +242,9 @@ def focalMmToCcdPixel(self, focalPlaneX, focalPlaneY):
-------
detectorName : `str`
The name of the detector.
ccdX : `int`
ccdX : `float`
The column pixel position relative to the corner of the detector.
ccdY : `int`
ccdY : `float`
The row pixel position relative to the corner of the detector.
Raises
Expand Down Expand Up @@ -275,13 +279,14 @@ def focalMmToAmpPixel(self, focalPlaneX, focalPlaneY):
-------
detectorName : `str`
The name of the detector.
channel: `int`
Channel number of amplifier (1-indexed; identical to HDU).
ampX : `int`
The column coordinate relative to the corner of the single-amp
image.
ampY : `int`
The row coordinate relative to the corner of the single-amp image.
ampName: `str`
The name of the amplifier.
ampX : `float`
The physical column coordinate relative to the corner of the
single-amp image.
ampY : `float`
The physical row coordinate relative to the corner of the
single-amp image.
Raises
------
Expand All @@ -293,85 +298,46 @@ def focalMmToAmpPixel(self, focalPlaneX, focalPlaneY):
amp, ampXY = ccdPixelToAmpPixel(ccdXY, detector)

ampX, ampY = ampXY
return detector.getName(), ampToChannel(amp), ampX, ampY


def ampToChannel(amp):
r"""Given an Amplifier, return the channel.
Parameters
----------
amp : `lsst.afw.table.AmpInfoRecord`
The amplifier in question.
Returns
-------
channel : `int`
The 1-indexed channel ID for the desired amplifier.
"""
return amp.get("hdu")


def channelToAmp(detector, channel):
r"""Given a Detector and channel, return the Amplifier.
Parameters
----------
detector : `lsst.afw.cameraGeom.Detector`
The requested detector.
channel : `int`
The 1-indexed channel ID for the desired amplifier.
Returns
-------
amp : `lsst.afw.table.AmpInfoRecord`
The amplifier in question.
"""
return [amp for amp in detector if amp.get("hdu") == channel][0]
return detector.getName(), amp.getName(), ampX, ampY


def ampPixelToCcdPixel(x, y, detector, channel):
def ampPixelToCcdPixel(x, y, detector, ampName):
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.
x : `float`
physical column on amp segment.
y : `float`
physical row on amp segment.
detector : `lsst.afw.cameraGeom.Detector`
The requested detector.
channel : `int`
Channel number of amplifier (1-indexed; identical to HDU).
ampName : `str`
The name of the amplifier.
Returns
-------
ccdX : `int`
ccdX : `float`
The column pixel position relative to the corner of the detector.
ccdY : `int`
ccdY : `float`
The row pixel position relative to the corner of the detector.
"""

amp = channelToAmp(detector, channel)
rawBBox, rawDataBBox = amp.getRawBBox(), amp.getRawDataBBox()
amp = detector[ampName]
ampBBox = amp.getBBox()
# Allow for flips (due e.g. to physical location of the amplifiers)
w, h = rawBBox.getDimensions()
w, h = ampBBox.getDimensions()
if amp.getRawFlipX():
rawBBox.flipLR(w)
rawDataBBox.flipLR(w)

x = rawBBox.getWidth() - x - 1
ampBBox.flipLR(w)
x = w - 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.
ampBBox.flipTB(h)
y = h - y - 1

return amp.getBBox().getBegin() + dxy + geom.ExtentI(x, y)
xyout = amp.getBBox().getBegin() + geom.ExtentD(x, y)

return xyout

def ccdPixelToAmpPixel(xy, detector):
r"""Given an position within a detector return position within an
Expand All @@ -393,37 +359,36 @@ def ccdPixelToAmpPixel(xy, detector):
-------
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.
ampXY : `lsst.geom.PointD`
The physical 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()
# Allow for flips (due e.g. to physical location and orientation
# of the amplifiers)
w, h = amp.getBBox().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
xy = geom.ExtentD (x, y)

return amp, xy

Expand Down

0 comments on commit 5b30495

Please sign in to comment.